Las tres capas de la web

Toda página web se compone de tres tecnologías con responsabilidades separadas. Entender esta separación es esencial para comprender dónde y cómo ocurren los ataques XSS.

HTML

La estructura

Define el contenido y su jerarquía. Es el esqueleto del documento: qué elementos existen, en qué orden, cómo se anidan.

<body>
  <h1>Título</h1>
  <p>Texto</p>
  <button>Click</button>
</body>

Sin CSS ni JS, HTML es texto plano estructurado. El navegador sabe qué hay, pero no cómo se ve ni qué hace.

CSS

La presentación

Controla el aspecto visual: colores, tipografía, espaciado, animaciones. No añade contenido ni comportamiento.

.btn {
  background: #ef3340;
  color: white;
  border-radius: 6px;
  padding: 8px 16px;
}

Sin JS ni HTML propio, CSS solo puede cambiar lo que ya existe en el DOM. No puede crear elementos nuevos.

JavaScript

El comportamiento

Añade interactividad y lógica: responde a eventos, modifica el DOM en tiempo real, hace peticiones al servidor, ejecuta código arbitrario.

document.querySelector('h1')
  .textContent = 'Hola mundo';
fetch('/api/data')
  .then(...)

JS tiene acceso total al DOM y puede leer document.cookie, hacer fetch a cualquier URL, redirigir al usuario...

¿Por qué importa esto para XSS?

XSS = JavaScript inyectado en una página legítima
Un ataque XSS inyecta código JavaScript en una página legítima. Una vez ejecutado, ese código tiene los mismos permisos que cualquier otro JS de la página: puede leer cookies, modificar el DOM, exfiltrar datos hacia servidores externos, capturar pulsaciones de teclado, o redirigir al usuario a páginas falsas.

Analogía: construir una casa

Una forma de recordar las tres capas y sus roles:

HTML
Paredes, suelo, techo

La estructura física. Define qué existe y dónde está. Sin ella no hay casa.

CSS
Pintura, decoración, muebles

El aspecto visual. Hace la casa habitable y agradable, pero no cambia su estructura.

JavaScript
Electricidad, fontanería, automatización

Lo que hace cosas. Puede encender luces, abrir puertas, enviar señales al exterior.

Separación de responsabilidades

La práctica estándar es mantener cada tecnología en su propio archivo. El navegador carga primero el HTML, y luego descarga los recursos referenciados:

HTML — index.html
<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="styles.css">  <!-- CSS externo -->
  </head>
  <body>
    <h1>Mi página</h1>
    <script src="app.js"></script>              <!-- JS externo -->
  </body>
</html>
Orden de carga y seguridad
El navegador primero carga el HTML, luego descarga CSS y JS referenciados. Este orden tiene implicaciones de seguridad: un script en el <head> bloquea la renderización; un script al final del <body> se ejecuta cuando el DOM ya está construido. Los ataques XSS pueden inyectar <script> en cualquier punto del HTML o aprovecharse del JS ya existente.

El viaje de una petición web

Entender el ciclo completo de carga de una página es clave para comprender por qué XSS basado en DOM puede ser invisible en las respuestas del servidor.

1

Resolución DNS

El navegador pregunta a un servidor DNS: ¿Cuál es la IP de 'ejemplo.com'? Recibe una respuesta con la dirección IP. Esta conversación es invisible para el usuario.

ejemplo.com → DNS → 93.184.216.34
2

Conexión TCP/TLS

El navegador establece una conexión TCP con el servidor en el puerto 443 (HTTPS). Se negocia TLS para cifrar el canal. Todo lo que se transmita a partir de aquí viaja cifrado.

3

Petición HTTP GET

El navegador envía la petición al servidor. Incluye la URL solicitada, cabeceras del navegador y las cookies del dominio.

HTTP Request
GET /buscar?q=laptop HTTP/1.1
Host: ejemplo.com
Cookie: session=abc123
4

Respuesta: el HTML inicial

El servidor procesa la petición y devuelve HTML. En XSS reflejado, el payload aparece aquí directamente en la respuesta.

HTTP Response — XSS Reflejado
<p>Resultados para: <script>alert(1)</script></p>
<!-- VISIBLE en la respuesta HTTP -->
XSS Reflejado
El payload está en la respuesta del servidor. Se puede ver en el código fuente de la página con Ctrl+U.
5

El navegador analiza el HTML

El parser HTML construye el DOM inicial. Encuentra referencias a recursos externos (CSS, JS, imágenes) y los descarga en paralelo. El árbol DOM empieza a existir en memoria.

6

CSS aplicado al DOM

Los estilos se calculan y aplican a cada nodo del DOM. El árbol de renderizado se construye. En este punto el usuario ve la página "pintada" con su apariencia visual.

7

JavaScript modifica el DOM

Los scripts se ejecutan. Aquí ocurren las peticiones AJAX/fetch para cargar datos dinámicos. El contenido puede cambiar radicalmente respecto al HTML inicial recibido del servidor.

JavaScript — SINK vulnerable
// JS lee la URL y escribe en el DOM
const q = new URLSearchParams(location.search).get('q');
document.getElementById('resultado').innerHTML = q; // SINK vulnerable
XSS basado en DOM
El payload NUNCA llega al servidor. El servidor devuelve HTML "limpio". El navegador ejecuta JS que lee la URL y escribe en el DOM sin sanitizar. No hay rastro en los logs del servidor.

¿Qué ve el servidor vs qué ve el usuario?

Lo que responde el servidor

  • HTML estático / plantilla renderizada
  • Datos de la base de datos
  • Lo que devuelve Ctrl+U (View Source)
  • XSS Reflejado: VISIBLE aquí

Lo que muestra el navegador

  • HTML inicial + modificaciones de JS
  • Contenido cargado vía AJAX/fetch
  • El resultado final de todas las mutaciones del DOM
  • XSS en DOM: SOLO visible aquí (F12 → Elements)

Implicación práctica de seguridad

Los escáneres HTTP no ven el DOM XSS
Un escáner que solo analiza la respuesta HTTP puede reportar un sitio como "seguro" aunque tenga un DOM XSS. El payload vive en la URL (#fragment) o se inserta vía JS después de la carga. Solo un navegador headless que ejecute JS puede detectarlo.

Comparativa de tipos de XSS

Tipo ¿Visible en respuesta HTTP? ¿Visible en DevTools Elements? ¿Persiste tras recargar?
Reflejado No
Persistente
DOM No No