PostMessage Security
Last updated
Last updated
postMessage()
Web browsers enforce a security measure known as the Same Origin Policy (SOP) to prevent websites from interfering with each other. This policy is crucial in safeguarding user data across the web.
Consider a scenario where you, as an attacker, have created a phishing page. On this page, you craft a GET request to https://examplebank.com/userAccount/getAccountInformation.aspx
. Now, imagine that a victim, who is already logged into their account at this bank, visits your phishing page. By default, the browser includes the user's cookies in any request made to the bank's website. This means that, since the victim is logged in, the request to getAccountInformation.aspx
will return the user's account information in the response.
If the Same Origin Policy didn't exist, the attacker could easily exploit this behavior. By loading the getAccountInformation.aspx
page in an invisible iframe within the victim's browser, the attacker could read the sensitive data returned in the response. This would allow them to steal the victim's account information without detection.
However, the Same Origin Policy acts as a security guideline that instructs the browser on which origins it allows to be read or modified by JavaScript. According to PortSwigger, the same-origin policy restricts scripts on one origin from accessing data from another origin. An origin is defined by a combination of the URI scheme, domain, and port number. For example, consider the following URL:
In this case:
Scheme: http
Domain (including subdomain): normal-website.com
Port number: 8080
These three components collectively form the origin for this page. For any other URI to be considered as coming from the same origin, all three components must match exactly. The Same Origin Policy ensures that no page from a different origin can read or modify the contents of this page, and vice versa.
With an understanding of the Same Origin Policy, it becomes clear why mechanisms like postMessage were introduced. These allow for controlled, secure communication between different origins when necessary.
The JavaScript postMessage()
function enables secure cross-origin communication between windows, such as a parent window and an iframe or a popup. This is especially useful when a web application needs to embed an iframe or open a new window, like a third-party contact form, and the parent and child windows have different origins. The Same Origin Policy (SOP) restricts communication between these windows, but postMessage()
allows them to exchange data securely without generating an HTTP request.
postMessage()
-> Sending Window:message: The data to be sent. It can be any JavaScript data type.
targetOrigin: Specifies the origin that the message is intended for. This should be a specific origin for security reasons, though a wildcard *
can be used to send the message to any origin.
transfer: (Optional) A sequence of Transferable Objects. These are transferred with the message but are not copied.
postMessage()
-> Recieving Window:"message"
: The event type you want to listen for. In this case, it's the "message"
event, which is triggered when a message is received from another window or frame.
function(event)
: A callback function that is executed when the "message"
event is fired. The event
object contains information about the message, such as:
event.data
: The data sent from the source window.
event.origin
: The origin of the message, which can be used to verify the message's source.
event.source
: A reference to the window that sent the message, which can be used to send a response back.
false/true
: The optional useCapture
parameter. If false
, the event is handled in the bubbling phase (default). If true
, the event is handled in the capturing phase.
In this example, the message is only processed if it comes from a trusted origin (https://trusted-domain.com
). This helps prevent malicious attacks such as Cross-Origin Communication attacks.
Detecting vulnerabilities related to postMessage()
is not always straightforward. It requires a solid understanding of JavaScript and the ability to analyze the target application’s JavaScript code to identify potential attack vectors. Tracing the execution flow is crucial for performing a successful attack.
To exploit postMessage()
vulnerabilities, you must first determine whether the target application utilizes web messaging. If it does, identifying the various listeners is critical. There are several methods to accomplish this, including:
Searching for Keywords:
Use the developer tool’s global search feature to look for specific keywords like postMessage()
, addEventListener("message")
, or .on("message")
in the JavaScript files. This can help pinpoint where the application is using postMessage()
.
Using the MessPostage Browser Extension:
MessPostage is a browser extension that simplifies detecting the use of postMessage()
APIs in an application. It highlights which messages were sent and where event listeners were added, making it easier to trace messaging activity.
Using Developer Tools:
The "Global Listener" feature in the "Sources" pane of Developer Tools can be used to identify the use of postMessage()
. By opening the Global Listener and clicking on "messages," you can view the message handlers that have been set up.
Using Posta:
Posta is a tool designed for researching Cross-document Messaging communication. It allows you to track, explore, and exploit postMessage()
vulnerabilities. Posta includes features like replaying messages sent between windows in any attached browser, providing valuable insights into the communication flow.
Using PMHook:
PMHook is a client-side JavaScript library that can be used with TamperMonkey in the Chrome web browser. It executes immediately at page load and wraps the EventTarget.addEventListener
method, logging any subsequent message event handlers as they are added. Additionally, it wraps the event handler functions to log the messages received by each handler.
While postMessage()
is a secure way to circumvent SOP, improper use can introduce vulnerabilities. Here are some common pitfalls:
Wildcard targetOrigin
:
Developers sometimes use *
as the targetOrigin
, allowing any origin to access the message. This is dangerous, especially if the message contains sensitive data. For example, if https://somewebsite.com
embeds an iframe from http://sub.somewebsite.com
and sends a postMessage with targetOrigin
as *
, an attacker can exploit this by changing the location of the iframe to their domain, intercepting the message.
Example:
An attacker can host a malicious webpage that changes the location of the iframe loaded by https://somewebsite.com
to http://attacker.com
using a simple script:
The message intended for somewebsite.com
will now be sent to the attacker's domain.
2. Insufficient/Lacking Origin Check:
On the receiving side, the message event listener should validate the origin of the message before processing it. Failure to do so can lead to vulnerabilities such as Cross-Site Scripting (XSS).
Example:
If the origin is not properly validated, an attacker could send a message containing an XSS payload that alters the page's content or steals sensitive data.
Insecure Example:
This example lacks validation, allowing an attacker to inject malicious code.
postMessage()
HTML5 postMessage introduces a new taint source in the form of the message payload (Event.data). A DOM-based Cross-Site Scripting (XSS) vulnerability occurs when the payload of a message event is handled in an unsafe way. The table below lists some of the most common functions and attributes that can lead to an XSS vulnerability.
The OCR extraction wasn't perfect, but I can clean it up and reconstruct the table for you:
Function | Description |
| The |
| Same as |
| Similar to |
| Similar to |
| Changing the page location could be exploited to perform an XSS attack by passing a |
| Similar to |
| Exploits similar to those with |
| Similar to |
| Markup passed directly to a jQuery selector is immediately evaluated, and any embedded JavaScript event handlers are executed. For example: |
| Data passed to the |
| Setting the “ |
| Many elements that support either a “href” or “src” attribute can be exploited to perform an XSS attack by setting a JavaScript: or Data: URI. Some examples include; SCRIPT, EMBED, OBJECT, A and IFRAME, however this is not an exhaustive list and new elements are introduced over time. |
postMessage()
Using iframe
In this scenario, we'll explore how a vulnerability in the postMessage()
implementation can be exploited using an iframe
. The scenario involves a lab environment with a vulnerable Node.js application.
Application Flow
Change Password Functionality:
The application has a "Change Password" feature that, when clicked, opens a child window for the user to update their password.
Password Update Process:
The user enters the new password and confirmation, then clicks "Save."
Upon saving, a message is displayed on the parent window indicating that the password was updated successfully.
Event Listener in the Parent Window:
The parent window contains an addEventListener
that listens for the message
event. This event is triggered whenever the parent window receives a message.
The event listener function updates the HTML content of an element with id="displayMessage"
upon receiving a message.
Source Code of the Child Window:
The child window sends an XHR request to update the password. Upon success, the server responds with a 200
status code and the message "Password update Successfull!".
The response is passed to the sendMessage
function.
The sendMessage
function uses postMessage()
to send the success message to the parent window, identifying the parent via window.opener
.
Vulnerability Analysis
Improper Origin Validation:
The postMessage()
method allows specifying the target origin. However, if the origin is not specified and a wildcard (*
) is used instead, the message could be intercepted or sent to any site.
In the vulnerable implementation, the parent window listens for messages but does not validate the origin of the incoming message, making it susceptible to attacks.
Exploitation Process
Understanding the Vulnerable Code:
The parent window is listening for messages, but without verifying the sender's origin. This opens up the possibility of an attacker sending malicious data to the parent window.
Secure Implementation:
A secure implementation would involve verifying the origin of the sender by comparing it with an acceptable origin. This prevents unauthorized messages from being processed.
Exploit Development:
HTML Structure:
Create an HTML page with a submit button.
Load the vulnerable application in an iframe
.
Exploit Function:
Define a payload
containing the malicious JavaScript code.
Use the contentWindow
property to access the iframe
's window object.
Send the payload to the iframe
using postMessage()
.
Explanation:
Line 7: The payload
contains a malicious script designed to trigger an XSS attack.
Line 8: The contentWindow
property allows access to the iframe
's window object, enabling manipulation of its DOM.
Line 9: The postMessage()
method sends the malicious payload to the iframe
, exploiting the lack of origin validation.
Deploy the Exploit:
Load the exploit page in a browser and click on the "Click to Exploit" button.
Result:
The XSS payload is executed in the context of the vulnerable application, demonstrating a successful attack.
Using indexOf
:
This check can be bypassed by registering a subdomain like https://legitimate.com.attacker.site
.
Using String.search
:
Bypass this by using a domain like legit.matesite.com
, where the .
will match any character due to the regex conversion, allowing it to match legitimatesite.com
.
Bypassing e.origin === window.origin
:
This sets e.origin
and window.origin
to null
, bypassing the check.
Some headers like X-Frame-Options
prevent a webpage from being iframed. To bypass this, you can open a new tab to the vulnerable web application and communicate with it:
Vulnerable Code
In a real-world scenario, a developer intended to allow multiple hostnames (http(s)://mail.google.com
and http(s)://www.google.com
) using a regular expression:
Issue: The use of unescaped dots (.
) in the regex, specifically in ".google.com"
, creates a vulnerability. In regular expressions, a dot (.
) matches any character, allowing unintended URLs like http://mailXgoogle.com
to pass the validation.
This issue can also occur when regexes are automatically generated based on the current page location, as seen in www.facebook.com
:
This generates:
Unescaped dots in regex-based validation can lead to security flaws by matching unintended URLs, potentially allowing malicious origins.
Another frequent error in regular expressions is failing to denote the end of the matched value using the $
symbol. Consider the following regular expression:
Issue: While the dots (.
) are correctly escaped, the pattern lacks a $
at the end, meaning it doesn't enforce that the match must end exactly at .google.com
. As a result, any domain that starts with the matched value is accepted, such as https://www.google.com.sec-1.com
.
Example: This is a common validation mistake, and tools like the PMHook replay tool exploit this by dynamically generating hostnames. For instance, if a message originally came from https://www.google.com
, the replay tool might open it at https://www.google.com.sentinel.appcheckng.com/
, potentially bypassing flawed regex validation.
Conclusion: Always use the $
symbol in regular expressions to ensure that the match ends exactly where intended, preventing acceptance of unintended domains.