postMessage Exploitation
Send cross-origin messages with arbitrary data, which can easily lead to Cross-Site Scripting in vulnerable handler that fail to verify the origin
Description
The window.postMessage API in JavaScript allows windows, even from different origins or sites, to communicate using messages. One window can register a listener, and another with a reference to the first window can send it any message. The listener will receive messages from any location and needs to handle the integrity of those messages by itself. See the following example:
The above will send a "Hello" message to the opened window, receiving the message and returning an echoed response. The original window will receive this and log the response as "echoed Hello".
Methodology
Step 1 - Finding postMessage uses: Use loggers such as postMessage-tracker or Burp Suite DOM Invader to find any messages an application sends while browsing the target application.
Step 2 - Checking sender targetOrigin: Find calls to postMessage(message, targetOrigin)
where the targetOrigin
is a wildcard ("*"
) or another origin that you control. If the window this method is called on is your domain, you can receive and read this message. The opener
variable may point to your domain if the target was opened as a new tab from there. If the message is sent to an iframe inside the target page, and the target page itself is framable, you can hijack the location of this inner iframe to intercept the message before it is sent.
Step 3 - Checking listener origins: Look for listeners registered via document.onmessage =
or addEventListener("message", ...)
and check if their function body verifies the e.origin
of the message correctly. This should be done against a static expected origin or the safe window.location.origin
variable. Even window.origin
is unsafe and vulnerable as explained in Bypassing window.origin using 'null' origin.
Step 4 - Finding vulnerable sinks: When a handler uses flawed logic to verify the origin, you can send arbitrary messages to it from your origin. Use debugging with breakpoints to follow what the handler does and if the e.data
reaches any dangerous sinks like eval(...)
or location = "javascript:..."
. Remember that your data must fit the Structured Clone Algorithm which disallows sending functions, but allows a lot more like strings, arrays, objects and some more complex types (more than just JSON).
Step 5 - Exploiting a vulnerability: After finding a vulnerable sink in a flawed origin check, write a simple HTML page on another origin to exploit it. If the target page allows it, it can be iframed without interaction, but cookies won't always be used. After an interaction with onclick =
you may open a window to the target page and hold that reference to send messages to exploit it. See the examples below for some different exploitation techniques.
Target Origin Check
As you can see, the first argument is for the data sent. The only requirement for this data is that it can be sent using the Structured Clone Algorithm. The second argument is more interesting for vulnerabilities as it defines the targetOrigin
. Because JavaScript doesn't know if the window's origin changed between loading the window and sending the message, this argument is another check that verifies the origin of the window you are sending a message to is the value in the string. The special "*"
symbol means a wildcard, or any origin (less secure).
If a website does not verify where it is sending a message, you may be able to receive a message that wasn't intended for you. One trick involving this idea is the fact that any origin can change the location of frames within an iframe. If we can iframe a target page that itself loads another iframe, we can change the location of this inner iframe to anything else to intercept messages. The following example uses example.com as the target domain, which loads an iframe of some other trusted page expecting a postMessage:
An attacker can abuse this by using the window.frames
property to change the location of this inner iframe, and intercept the secret message:
Handler Origin Check
Another more common vulnerability is vulnerable handlers. If a target site creates a listener with code that does not verify the origin (e.origin
), it may parse untrusted data. If this wasn't expected, the data may end up in dangerous sinks and allow vulnerabilities like XSS or leaking data through a response to e.source
.
Finding handlers
You can find all listeners manually by checking Global Listeners in the DevTools -> Sources tab:
Another option is using extensions that automatically log every message being sent. This allows you to get a quick idea of what kind of data is being sent, and if it may be sensitive or dangerous:
An example of a dangerous handler is the following. Note that it does not verify the origin of the message, and the sent data ends up in a dangerous sink (eval
):
An attacker can exploit this by iframing (1) the above page, or by opening a top-level window (2) to it. Then they need to send a postMessage
to this window that will exploit the sink:
Notably, cookies and other resources like localStorage are not available in a third-party context like an iframe where the top-level (address bar) origin is different from the target origin. The only exception to this is SameSite=None cookies which are readable or fetch same-origin resources without any CORS limitations.
This means that to really exploit an XSS, you need to use a window instead of an iframe because its top-level origin is the same as the target origin. You will be able to read any non-httpOnly cookies through document.cookie
, or use any cookies in a fetch()
without CORS limitations, gaining full XSS impact.
Stealthier: tab-under method
Another stealthier way of doing the above is by never letting the user see the target website, only our attacker-controlled website. To do this, we can follow the idea below:
Victim visits the attacker's website
On interaction, use
window.open
to open a new window to an attacker's page again ("2nd window"). This will gain the focus of the browser nowNow the initial window changes its own location to the target page containing a vulnerable postMessage handler, this time in a top-level context
The 2nd window will now use
opener.postMessage
as its reference to the vulnerable domain and send the exploit to the postMessage handler. The resulting XSS will now have full access to cookies etc.
Bypassable origin checks
Websites often protect against the vulnerabilities above by checking the message origin at each handler. It will refuse to execute the potentially dangerous code if it doesn't match the expected origin. The following common examples are safe:
This requires knowing the exact origin beforehand and comparing it against it, or the frame's origin being the same as the current location. Some developers make this more generic by relaxing the condition slightly, but this can quickly lead to vulnerabilities:
Bypassing window.origin using 'null'
origin
'null'
originAnother more tricky condition to bypass is the following (source):
The above is exploitable because using iframe sandboxes, e.origin
as well as window.origin
can both be set to 'null'
. After doing so, SameSite=Lax cookies will be used to initiate the top-level request and may have placed a secret CSRF token or user data in the HTML, which can be read by exploiting the postMessage
handler. The following was a CTF challenge that required you to use this novel technique to steal another identifier causing XSS:
https://twitter.com/terjanq/status/1446500485142355972
The technique goes as follows:
Victim visits the attacker's website
Create an iframe with a strict sandbox (excluding
allow-same-origin
) to make its origin'null'
Inside the
srcdoc
of this frame, open a window to the vulnerable target page after interaction. This will inherit the'null'
origin so the source and destination are the same, which will bypass the checkAfter the message listener is registered, send a postMessage with the exploit to cause XSS in a
'null'
originUse this XSS to leak any data on the current page. You cannot access resources like cookies or localStorage, and fetches will exclude any cookies because the origin is
'null'
and SOP is in effect. The idea is to read a CSRF token on the vulnerable page and exfiltrate it for another attack
Below is an example of a vulnerable page that registers a message listener and shows sensitive content on the page (cookies directly, but this may be a CSRF token or user data):
This can be exploited with a single click on the following attacker's page:
Last updated