The pitfalls of postMessage

labsdetectify

The postMessage API is an alternative to JSONP, XHR with CORS headers and other methods enabling sending data between origins. It was introduced with HTML5 and like many other cross-document features it can be a source of client-side vulnerabilities.

 

How it works

To send a message, an application simply calls the "postMessage" function on the target window it would like to send the message to:

targetWindow.postMessage("hello other document!", "*");

 

And to receive a message, a “message” event handler can be registered on the receiving end:

window.addEventListener("message", function(message){console.log(message.data)});

 

Pitfall 1

The first pitfall lies in the second argument of the “postMessage” function. This argument specifies which origin is allowed to receive the message. Using the wildcard “*” means that any origin is allowed to receive the message.

 

Since the target window is located at a different origin, there is no way for the sender window to know if the target window is at the target origin when sending the message. If the target window has been navigated to another origin, the other origin would receive the data.

 

Pitfall 2

The second pitfall lies on the receiving end. Since the listener listens for any message, an attacker could trick the application by sending a message from the attacker’s origin,  which would make the receiver think it received the message from the sender’s window. To avoid this, the receiver must validate the origin of the message with the “message.origin” attribute.

 

If regex is used to validate the origin, it’s important to escape the “.” character, since this code:

 

//Listener on http://www.examplereceiver.com/

window.addEventListener("message", function(message){

if(/^http://www.examplesender.com$/.test(message.origin)){

console.log(message.data);

}

});

 

Would not only allow messages from “www.examplesender.com“, but also “wwwaexamplesender.com“, “wwwbexamplesender.com” etc.

 

Pitfall 3

The third pitfall is DOM XSS by using the message in a way that the application treats it as HTML/script, for example:

 

//Listener on http://www.examplereceiver.com/

window.addEventListener("message", function(message){

if(/^http://www\.examplesender\.com$/.test(message.origin)){

document.getElementById("message").innerHTML = message.data;

}

});

 

DOM XSS isn’t postMessage specific, but it’s something that’s present in many postMessage implementations. One reason for this could be that the receiving application expects the data to be formatted correctly, since it’s only listening for messages from http://www.examplesender.com/.

 

But what if an attacker finds an XSS bug on http://www.examplesender.com/? That would mean that they could XSS http://www.examplereceiver.com/ as well.

 

Not using postMessage?

You might be thinking “our application doesn’t use postMessage so these issues doesn’t apply to us”. Well, a lot of third party scripts use postMessage to communicate with the third party service, so your application might be using postMessage without your knowledge.

 

You can check if a page has a registered message listener (and which script registered it) by using Chrome Devtools, under Sources -> Global Listeners:

 

postMessage pitfalls

 

Some third party scripts fall into these pitfalls. In the next post I will describe a postMessage vulnerability that caused XSS on over a million websites, and the technique I used to find it.

References

https://seclab.stanford.edu/websec/frames/post-message.pdf

https://www.w3.org/TR/webmessaging/

http://caniuse.com/#feat=x-doc-messaging

 


Author:
Mathias Karlsson
Security Researcher
@avlidienbrunn