Tema 6
Cross-Site Scripting, o XSS, aparece cuando una aplicación permite que contenido controlado por un atacante termine ejecutándose en el navegador de otro usuario. Es una vulnerabilidad especialmente peligrosa porque convierte al navegador legítimo en el vehículo del ataque.
Una aplicación web normalmente recibe datos, los procesa y luego los devuelve al navegador en forma de HTML, atributos, scripts, estilos o contenido dinámico. Si esa salida se construye sin controles adecuados, el navegador puede interpretar como código algo que en realidad proviene del atacante.
Ese es el núcleo de XSS: el sistema entrega al navegador contenido que no debería ejecutarse, pero termina ejecutándose dentro del contexto del sitio legítimo.
La gravedad de XSS no depende solo del script en sí. Depende del contexto donde se ejecuta: qué puede leer, qué sesión acompaña al usuario, qué acciones permite la aplicación y qué datos están disponibles en esa página.
Cross-Site Scripting no consiste simplemente en "inyectar JavaScript". Es una falla de confianza entre datos y contexto de salida. Ocurre cuando el sistema toma entrada no confiable y la inserta en una respuesta o en el DOM sin asegurarse de que sea tratada como dato y no como instrucción.
El navegador no distingue intenciones del desarrollador. Solo interpreta el resultado final que recibe. Si en ese resultado aparece una secuencia válida como código, la ejecutará según las reglas del documento y del origen.
Cuando un XSS se explota, el código malicioso se ejecuta en el navegador de la víctima como si fuera parte del sitio legítimo. Eso puede permitir:
Incluso si una cookie de sesión está marcada como `HttpOnly`, XSS sigue siendo grave porque el código puede usar el contexto autenticado del usuario para disparar acciones o leer datos expuestos en la interfaz.
Existen tres categorías clásicas que ayudan a entender cómo llega el contenido malicioso al navegador.
Estas categorías son útiles para estudiar el problema, aunque en sistemas reales pueden combinarse o solaparse.
En el XSS reflejado, el atacante logra que la aplicación devuelva en la respuesta contenido controlado por él, por ejemplo desde una URL, búsqueda o parámetro. La aplicación "refleja" ese dato sin tratarlo adecuadamente, y el navegador lo interpreta.
Este tipo de XSS suele requerir que la víctima visite un enlace especialmente preparado o interactúe con una solicitud manipulada. Por eso muchas campañas lo combinan con phishing o engaño social.
En el XSS almacenado, el atacante introduce el contenido malicioso en un lugar persistente de la aplicación, por ejemplo comentarios, perfiles, publicaciones, tickets o mensajes. Luego, cada usuario que visualiza ese contenido recibe la carga maliciosa desde el propio sitio legítimo.
Este tipo suele ser especialmente grave porque no depende de convencer individualmente a cada víctima. Una vez almacenado, el ataque puede propagarse a múltiples usuarios o incluso a administradores.
En el DOM XSS, la vulnerabilidad no está necesariamente en la respuesta HTML generada por el servidor, sino en cómo el JavaScript del cliente procesa datos no confiables, por ejemplo desde la URL, `location`, `document.referrer`, `postMessage`, almacenamiento local o fragmentos del documento.
Si ese código inserta datos en el DOM mediante APIs inseguras, el navegador puede crear nodos interpretables y ejecutar contenido no previsto.
Esta variante es importante en aplicaciones modernas con frontend rico, donde gran parte de la lógica de interfaz vive en el navegador.
La defensa contra XSS depende en gran medida del contexto donde se inserta el dato. No existe una única codificación universal que sirva para todo. El navegador interpreta de manera distinta un valor según si está en:
Por eso escapar salida correctamente significa aplicar la codificación adecuada para el contexto exacto. Un escape correcto para HTML puede ser insuficiente o incorrecto para un atributo o un script.
| Contexto | Riesgo | Defensa principal |
|---|---|---|
| Texto HTML | Interpretación como etiquetas o entidades | Escape HTML por contexto |
| Atributos HTML | Ruptura del atributo o inserción de eventos | Escape adecuado y evitar atributos inseguros |
| JavaScript embebido | Construcción de código ejecutable | Evitar insertar datos en script inline |
| URLs | Esquemas peligrosos o redirecciones indebidas | Validar protocolos y codificar contexto URL |
| DOM dinámico | Inserción insegura vía APIs del navegador | Usar sinks seguros y evitar HTML arbitrario |
En análisis de DOM XSS suele hablarse de fuentes y sinks.
El riesgo aparece cuando una fuente no confiable llega a un sink peligroso sin validación, sanitización o transformación segura.
Dependiendo del contexto de la aplicación, un XSS puede producir distintos impactos:
Un XSS en un panel de administración suele ser mucho más grave que uno en una página pública sin sesión ni datos sensibles.
Durante mucho tiempo se asoció XSS principalmente al robo de cookies. Aunque eso sigue siendo relevante en ciertos escenarios, hoy el impacto es más amplio. Aun cuando las cookies estén protegidas con `HttpOnly`, el atacante puede utilizar el contexto de la sesión activa para enviar solicitudes, leer datos renderizados o modificar la interfaz.
Por eso marcar cookies como `HttpOnly` ayuda, pero no resuelve por sí solo el problema de XSS.
La defensa técnica más importante contra XSS del lado servidor es escapar la salida según el contexto donde se inserta el dato. El objetivo es que el navegador interprete ese valor como contenido literal y no como estructura ejecutable.
Esto implica:
Hay casos donde el negocio necesita aceptar contenido enriquecido, por ejemplo descripciones con formato, comentarios avanzados o editores WYSIWYG. En esos escenarios no alcanza con escape total, porque justamente se quiere permitir una parte limitada del markup.
Allí se necesita una sanitización robusta basada en una política clara de qué etiquetas, atributos y comportamientos están permitidos. Esta tarea debe hacerse con herramientas específicas y bien mantenidas. Hacerlo manualmente suele ser frágil.
Muchos frameworks modernos de frontend y backend escapan contenido por defecto en contextos comunes. Esto reduce bastante el riesgo, pero no autoriza a bajar la guardia. La vulnerabilidad puede reaparecer cuando:
La herramienta ayuda, pero la comprensión del riesgo sigue siendo necesaria.
Content Security Policy, o CSP, es una cabecera de seguridad que ayuda a restringir qué scripts, estilos y recursos puede cargar o ejecutar una página. Bien configurada, puede mitigar ciertos escenarios de XSS o al menos dificultar su explotación.
Sin embargo, CSP no reemplaza el escape de salida ni la sanitización. Se considera una capa adicional de defensa, útil especialmente para limitar ejecución de scripts inline o cargas desde orígenes no esperados.
En aplicaciones con JavaScript del lado cliente, la elección de APIs del DOM importa mucho. Algunas operaciones insertan texto literal y otras insertan HTML interpretable. Desde la seguridad, conviene preferir aquellas que tratan el contenido como texto cuando el negocio no requiere markup.
La regla práctica es sencilla: si solo necesitas mostrar texto, usa mecanismos que inserten texto. Reservar inserción de HTML para casos estrictamente necesarios reduce muchísimo el riesgo.
Imaginemos un sistema de soporte donde los usuarios pueden enviar tickets y los administradores los leen desde un panel interno. Si el campo de mensaje acepta contenido que luego se renderiza sin control, un atacante podría insertar un payload que se ejecute cuando el operador abra el ticket.
En ese caso el XSS no afecta solo al usuario que escribió el mensaje, sino al operador o administrador que tiene más privilegios. El impacto cambia por completo según quién consuma el contenido vulnerable.
Una estrategia sólida suele combinar varias capas:
XSS es una vulnerabilidad especialmente importante porque ataca la confianza entre aplicación y navegador. Cuando una salida se construye sin cuidado, el código del atacante puede ejecutarse con los privilegios y el contexto del sitio real, afectando directamente a usuarios y administradores.
En el próximo tema estudiaremos `CSRF`, otra vulnerabilidad del lado navegador, pero con una lógica distinta: no busca ejecutar código en la víctima, sino inducir su navegador autenticado a realizar acciones no deseadas.