Exploiting postMessage()

Chirag Rai
8 min readMay 28, 2021

--

Why is postMessage() used?

According to Mozilla,

Normally, scripts on different pages are allowed to access each other if and only if the pages they originate from have the same origin, i.e; they share the same protocol, port number, and host (also known as the “same-origin policy”). window.postMessage() provides a controlled mechanism to securely circumvent this restriction (if used properly).

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

postMessage( ) is used to send data locally between the Window objects and does not generate an HTTP request to send data. It is used for DOM based communication.

postMessage() Syntax:

targetWindow.postMessage(message, targetOrigin, [transfer])

  • targetWindow: A reference to the window that will receive the message. Examples of such references include window.open, window.opener, window.parent, window.frames, etc.
  • message: Data to be sent.
  • targetOrigin: The origin of the targetWindow that is supposed to receive the message. If the targetWindow origin does not match the targetOrigin specified here, the message won’t be dispatched at all. This adds a layer of security to the postMessage() functionality. The targetOrigin can also be “*” which means that the targetWindow can have any origin. However, one needs to be pretty careful while using “*” with sensitive data being passed in the functionality.
  • transfer (Optional): It is a sequence of Transferable objects that are transferred with the message.

Note- If the targetWindow has file:// protocol used in the origin, use “*” as targetOrigin in the postMessage method call. An example of this is when files are hosted locally.

The targetWindow can listen to the dispatched messages using an event listener as such-

window.addEventListener("message", function()=> {
//some action to perform with the message data
}, false/true);

Here message is the data that is sent from source window to the targetWindow and the function describes the actions to be performed with the data received. The false/true at the end is optional useCapture parameter that tells if the event should be executed in the capturing or in the bubbling phase (False by default). OnMessage is another example of event handler that can be used to receive messages.

Basic functioning of postMessage():

Here we have an application which uses postMessage() to send data from the current window to an iframe embedded within it.

The code is as follows:

Page1.html

<html> <body>

<iframe src=”Page2.html” onload=”this.contentWindow.postMessage(‘Hi user’,‘https://www.example.com’)”></iframe> //Message sent via postMessage() to the embedded Iframe

</body> </html>

Page2.html

<html>
<body>
<script>
window.addEventListener(‘message’, function(e){

if(e.origin== “https://www.example.com”)
{document.write(document.location+“ says ”+e.data);}
}); //Event listener that checks for origin and writes data accordingly
</script>
</body>
</html>

Page2.html loaded in an iframe within Page1.html

Here Page1.html loads Page2.html in an iframe and sends a message “Hi user” to it via postMessage() which is displayed here in the embedded iframe.

Vulnerabilities present in postMessage()

  • Vulnerable application does not check the origin of the website that it receives the message from and incorporates the received data into its own application without proper validation.

An attacker may load the vulnerable site within an iframe in a malicious site controlled by the attacker or may spawn a child window of the vulnerable application from an attacker controlled site and send post message to the vulnerable site containing a malicious payload (possibly an XSS payload). Since the vulnerable site does not check the origin of the message received via postMessage() call here and incorporates the received message into the application, the attacker is able to successfully execute the payload in the context of the vulnerable application. Kindly note, loading the vulnerable site in an iframe in a malicious site will fail in case the vulnerable site uses proper X-Frame -Options or an appropriate CSP header or any type of frame busting code, since the site won’t load in an iframe due to these mitigations. Hence, this is somewhat a way to validate the origin of the sender, however, it can be easily bypassed by opening the vulnerable application as a child window instead of in an iframe.

Lets have a look at the example code

Suppose an application contains a vulnerable page which evaluates the data received via postMessage() without any validation, an attacker can send malicious payloads to the application which gets executed in the context of the vulnerable application.

Below mentioned is the vulnerable code:

<html>
<body>
<script>
window.addEventListener(‘message’, function(e){
eval(e.data);
}); ///Insecure event listener
</script>
</body>
</html>

Lets call this ‘vulnerable page.html’

An attacker hosts a malicious page on his website with the following code

<html> <body>

<iframe src=”vulnerable page.html” onload=”this.contentWindow.postMessage(‘alert(document.location)’,‘*’)”> //Malicious Payload sent via PostMessage to the Iframe

</iframe></body> </html>

This code loads the vulnerable page in an iframe and sends to it a postMessage with a malicious XSS payload as message using the onload event. The targetOrigin here can be ‘*’ or the vulnerable site domain. Lets call this page ‘malicious page.html’

Now the attacker lures the victim to open the malicious page in his browser which then opens the vulnerable page in an iframe and the malicious payload gets executed in the context of the vulnerable application as shown below.

Payload Executed

Depending on the functionality of the vulnerable application and how the received data is used, the severity of this attack can be further increased.

  • Vulnerable application does not specify the target origin that it needs to send the data to, in the postMessage() function’s targetOrigin parameter

When using postMessage to send a message if the targetOrigin is not explicitly specified as a domain and ‘*’ is used instead, then any malicious website can read the sensitive information sent via postMessage. For eg; if a malicious website spawns a child window containing the vulnerable application that will send credentials to its parent window via postMessage and this child window does not specify the domain that is supposed to receive the message i.e; postMessage(message,‘*’) is used, then the malicious parent website that has spawned the child window can easily steal the user credentials from the child window by adding an event listener on its page that will listen to the postMessage coming from the vulnerable child window.

Lets have a look at the example code for this kind of attack. Here, the vulnerable page contains a sensitive field called Secret Password. This page sends the user provided input for the field to its Parent Window (Reference to the window that spawned this vulnerable page as a child) as soon as the user clicks on Submit. Lets call this page ‘vulnerablepage.html’ and below is the code for the same.

<html>
<body>
<form method=”post” id=”myForm”>
Secret Password:<input type=”password” id=”password” />
<button type=”submit” id=”submit” onclick=window.parent.postMessage(document.getElementById(“myForm”).elements[0].value,’*’)>Submit</button> //Data sent to parent window
</form></body>
</html>

An attacker may embed this vulnerable page within an iframe in an attacker controlled site. Lets call this attacker controlled page- ‘maliciouspage.html’ which will have ‘vulnerablepage.html’ embedded within it. Below mentioned is the code for the same.

<html>
<body>
<iframe src=”vulnerablepage.html” width=500 height=500></iframe>
<script>
window.addEventListener(‘message’, function(e){
alert(e.data);
}); //Event listener used to receive data from child window and display it
</script>
</body></html>

Maliciouspage.html has Vulnerablepage.html loaded in an Iframe

As soon as the victim user enters his Secret Password in the vulnerable page iframe here, and clicks on ‘Submit’. The vulnerable page will send a message containing the Secret Password to its parent window which in this case is ‘maliciouspage.html’. The ‘maliciouspage.html’ listens to the data sent using the event listener defined above and displays the same in an alert. However, in a real time attack, an attacker can just as easily send this sensitive data to his own website instead of displaying it, hence, giving him access to it.

Sensitive Information received by Attackers Page (Maliciouspage.html)

All the attacker requires to do here, is to lure the victim into opening the ‘maliciouspage.html’ in his browser. Based on the sensitivity of the data sent, the severity of this attack can be escalated.

Now another big question is how to find this vulnerability?

I am afraid the answer is not quite simple. Since this is a vulnerability present in the client-side implementation of postMessage() function, finding it can be as difficult as finding any other DOM based vulnerability. However, while going through the Javascript code searching out for terms like postMessage and addEventListener (or any other event listener) will definitely be helpful. One more thing you may do, is to use the browsers developer tools to check for all the event listeners present in a particular page and search for ‘message’ as shown below.

Event Listener displayed in Browser’s Developer Tools

How can we mitigate this vulnerability?

Mitigations for the above described vulnerabilities can be as follows

  • Clearly define the targetOrigin when sending data via postMessage. Do not make use of ‘*’ as targetOrigin unless required.
  • If you are receiving data via postMessage, check the source origin of the data. If it does not match your requirement, discard the data.
  • Setting appropriate X-Frame-Options and Content-Security-Policy headers will prevent the page from being loaded in an iframe.
  • Additionally, properly validate the data sent via postMessage() as required.

At last I would like to give due credit to all those papers and blogs that helped me bring together this post

References:

Practise Labs-

--

--

Chirag Rai

A Cyber Security Enthusiast by heart and a Security Consultant by profession. Twitter: https://twitter.com/crai_in