XSS Through HTML5 PostMessage()

XSS with HTML5 postMessage() #

This is going to be a technical dive into the new HTML5 postMessage() method which can be exploited to launch XSS attacks against a site which otherwise was properly filtering client provided input.

The Method #

The postMessage() method was created to enable cross-document messaging from applications on separate domains. In other words, it effectively side steps the same-origin policy allowing applications to send and receive data to and from each other. Here is the syntax for the method:

otherWindow.postMessage(message, targetOrigin, [transfer]);

Let’s take the following example:

Site A has a nested iframe of Site B, which is on a separate domain:
SiteASiteB

If Site A wants to read content from Site B using a script like this:

Site A:

<iframe src="http://site-b.net/" name="siteb"></iframe>

<script>
document.getElementsByName('siteb')[0].onload = function() {
    frames[0].getElementById("message").innerHTML = "Hello World.";
}
</script>

Site B:

<div id="message"></div>

It would receive a same-origin error saying “Error: SecurityError: Blocked a frame with origin ”http://site-a.com“ from accessing a cross-origin frame.” Now, thanks to our new postMessage() method, we can avoid this error by using the following code:

Site A:

<iframe name="siteb" src="http://site-b.net"></iframe>

<button onclick="frames[0].postMessage('Hello World.','*')">Send Message</button>

Site B:

<div id="message"></div>
<script>
window.addEventListener('message', writeMessage, false);

function writeMessage(event)
{
    document.getElementById("message").innerHTML = event.data;
}
</script>

Which would result in Site A sending the message “Hello World” to Site B and writing it to the DOM (Document Object Model).
SiteASiteB

The Vulnerability #

Now that the postMessage() method allows us to communicate between iframes, we essentially have created a new attack vector for exploiting XSS. In many cases developers are not sanitizing the data that is passed in between applications in this way. The mistake is assuming that an application will only send you appropriate, safe data. Let’s look at the same example as before but from the perspective of an attacker.

Because Site B is actively listening for any site to send it a message, the attacker can load Site B as an iframe in an evil webpage that he controls, Site X.
SiteASiteB

Now he can send a malicious XSS message to Site B using the postMessage() method:

Site X:

<iframe name="siteb" src="http://site-b.net"></iframe>

<button onclick="frames[0].postMessage('<img src=x onerror=alert(document.cookie)','*')">Send Attack</button>

Site B:

<div id="message"></div>
<script>
window.addEventListener('message', writeMessage, false);

function writeMessage(event)
{
    document.getElementById("message").innerHTML = event.data;
}
</script>

SiteASiteB

This malicious message was sent from the attacker controlled webpage to Site B, then was written to the DOM and executed as javascript. This resulted in the alert box containing the session id; but as we discussed last time, we can go much further than an alert box.

The Solution #

There are two recommendations for securing the postMessage() method. The first is to specifically designate the target origin when sending messages to another application. This prevents the message from being “snooped” on by other iframes on the page which may have listeners setup. In our example, the targetOrigin should be as follows:

frames[0].postMessage('Hello World.','http://site-b.net');

On the receiving side, all listeners should verify the event.origin parameter before accepting any messages. This will verify that any received messages had come from the expected application, and can be done by changing our Site B code to the following:

<div id="message"></div>
<script>
window.addEventListener('message', writeMessage, false);

function writeMessage(event)
{
    if (event.origin == "http://site-a.com") {
        document.getElementById("message").innerHTML = event.data;
    } else {
        return;
    }
}
</script>

As always, any data input to the application should be sanitized and filtered before being returned to the user. Javascript doesn’t have a built in function to make this easy, but you can either use a library such as JQuery, or write your own script to encode dangerous characters.

 
5
Kudos
 
5
Kudos

Now read this

Maltego OpenSSL Heartbleed Transform

Since the release of the OpenSSL Heartbleed attack, there have been many scripts, websites, and offered services to perform checks on whether a site is vulnerable to this devastating attack. Each check has its benefits and specific... Continue →