PostMessage Vulnerabilities. Part I

Title

This is the first part of a two series of articles about postMessage vulnerabilities. This part is an introduction to what is a postMessage, basic exploitation, detection and mitigation. The second part is an analysis of real cases reported in Bug Bounty scenarios.


What is a postMessage?

According to Mozilla:

The window.postMessage() method safely enables cross-origin communication between Window objects; e.g., between a page and a pop-up that it spawned, or between a page and an iframe embedded within it.

Let’s see an example.

Supose we have a main website (1.html) that communicates with another website (2.html). In the second website there is a back button that changes when the navigation in the first website changes. For example, in website1 we navigate to “changed.html”, then the back button in website2 points to “changed.html”. To do that, postMessage is used and sends the value of website1 to website2.

The code in 1.html is the following:

<!DOCTYPE html>
<html>
<head>
    <title>Website 1</title>
 <meta charset="utf-8" />
<script>

var child;
function openChild() {child = window.open('2.html', 'popup', 'height=300px, width=500px');
  
}
function sendMessage(){
    let msg={url : "changed.html"};
    // In production, DO NOT use '*', use toe target domain
    child.postMessage(msg,'*')// child is the targetWindow
    child.focus();
}</script>
</head>
<body>
    <form>
        <fieldset>
            <input type='button' id='btnopen' value='Open child' onclick='openChild();' />
            <input type='button' id='btnSendMsg' value='Send Message' onclick='sendMessage();' />
        </fieldset>
    </form>
</body>
</html>

There are two buttons:

  • The first one opens a popup containing 2.html through openChild() function
  • The second one sends a message through sendMessage() function. To do that a message is set defining msg variable and then calling postMessage(msg,'*').

The code in 2.html is the following:

<!DOCTYPE html>
<html>
<head>
    <title>Website 2</title>
    <meta charset="utf-8" />
    <script>
    // Allow window to listen for a postMessage
    window.addEventListener("message", (event)=>{
        // Normally you would check event.origin
        // To verify the targetOrigin matches
        // this window's domain
         document.getElementById("redirection").href=`${event.data.url}`;
        // event.data contains the message sent
      
    });function closeMe() {
        try {window.close();
        } catch (e) { console.log(e) }
        try {self.close();
        } catch (e) { console.log(e) }}
    </script>
</head>
<body>
    <form>
        <h1>Recipient of postMessage</h1>
            <fieldset>
                <a type='text' id='redirection' href=''>Go back</a>
                <input type='button' id='btnCloseMe' value='Close me' onclick='closeMe();' />
            </fieldset>
   
    </form>
</body>
</html>

There is a button and a link:

  • The link handles the back redirection. The href field changes according the data received with the listener window.addEventListener("message", (event). After receiving the message, the data in the event is read from event.data.url and passed to href.
  • The button closes the window calling the function closeMe()

Basic diagram


A basic vulnerability

PostMessages if are not implemented correctly could lead to information disclosure or cross-site scripting vulnerabilities (XSS).

In this case, 2.html is expecting a message without validating the origin, therefore we could host a webpage 3.html that will load 2.html as an iframe and the invoke the postMessage() function to manipulate the href value.

<!DOCTYPE html>
<html>
<head>
    <title>XSS PoC</title>
 <meta charset="utf-8" />


</head>
<body>
    
 <iframe id="frame" src="2.html" ></iframe>

 <script>
 	
	let msg={url : "javascript:prompt(1)"};
	var iFrame = document.getElementById("frame")
	iFrame.contentWindow.postMessage(msg, '*');

</script>
</body>
</html>

In this example, the malicious msg variable contains the data {url : "javascript:prompt(1)"};, that will be send to 2.html. 2.html after processing, will change the value in the <a href to the value of the msg.url. An iframe is used to load in the same website the attack scenario. When a user clicks the Go back link, a XSS will be executed.

Attack diagram

Attack diagram

This is a simple case, in PostMessage Vulnerabilities. Part II real case scenarios will be analyzed.


Mitigations

According to Mozilla:

If you do not expect to receive messages from other sites, do not add any event listeners for message events. This is a completely foolproof way to avoid security problems.

If you do expect to receive messages from other sites, always verify the sender’s identity using the origin and possibly source properties. Any window can send a message to any other window, and you have no guarantees that an unknown sender will not send malicious messages. Having verified identity, however, you still should always verify the syntax of the received message. Otherwise, a security hole in the site you trusted to send only trusted messages could then open a cross-site scripting hole in your site.

Always specify an exact target origin, not *, when you use postMessage to send data to other windows. A malicious site can change the location of the window without your knowledge, and therefore it can intercept the data sent using postMessage.

In the previous scenario, the following code should be modified in 1.html:

child.postMessage(msg,'*')

to

child.postMessage(msg,'2.html')

And in 2.html:

window.addEventListener("message", (event)=>{
	...

}

To:

window.addEventListener("message", (event)=>{
	if (event.origin !== "http://safe.com")
    return;
	...
}


Detection

The way to detect postMessage vulnerabilities is reading JavaScript code. There is not an easy automated tool to help with this because when a listener is defined, the event data flow must be followed to analyze if the code ends in a vulnerable function. Anyways, I recommend two ways to detect the function calls:

With J2EEScan, from git repository, not BApp Store because I think is not updated:

With BurpBounty, defining a set of Passive response strings searching for keywords like: postMessage , addEventListener("message, .on("message".


References