XSS (cross-site scripting): qué es, tipos y cómo se evita
Guía en castellano sobre cross-site scripting (XSS): tipos reflejado, almacenado y DOM, dónde buscarlo en bug bounty y cómo se corrige.
Qué es
Un XSS ocurre cuando una web incluye en una página un dato que controla el atacante y el navegador lo interpreta como código en lugar de como texto. Ese código se ejecuta con la sesión de quien visita la página: puede leer lo que ve, hacer peticiones en su nombre o cambiar el contenido.
Hay tres variantes principales:
- Reflejado: el código viaja en la propia petición (un parámetro de la URL) y la víctima tiene que abrir un enlace preparado.
- Almacenado: el código se guarda en la web (un comentario, un nombre de perfil, un fichero subido) y se ejecuta en todo el que lo vea. Suele ser el más grave.
- Basado en DOM: el fallo está en el JavaScript del cliente, que toma un dato (por ejemplo
location.hash) y lo mete en la página con algo comoinnerHTML.
Dónde suele aparecer
- Buscadores, mensajes de error y cualquier sitio que repita lo que escribes.
- Editores de texto enriquecido, Markdown o firmas de correo con filtros caseros.
- Atributos HTML y bloques
<script>donde se inserta un dato sin el escapado adecuado para ese contexto. - Subida de ficheros SVG o HTML servidos desde el mismo dominio.
- Enlaces con esquema
javascript:en campos de URL.
Cómo se busca
- Introduce una cadena única e inofensiva (por ejemplo
xsstest123"'<>) en cada campo y busca dónde aparece en la respuesta. - Mira en qué contexto cae: texto HTML, dentro de un atributo, dentro de JavaScript o en una URL. Cada contexto necesita una técnica distinta.
- Comprueba qué caracteres sobreviven sin escapar. Si
<y>llegan intactos al HTML, el filtro no está haciendo su trabajo. - Para el DOM, revisa en el código del cliente qué fuentes (
location,postMessage, almacenamiento) acaban en funciones peligrosas (innerHTML,eval,document.write).
Para la prueba de concepto basta con algo visible y sin daño, como mostrar el dominio con alert(document.domain).
Cómo se evita
- Escapar siempre la salida según el contexto (HTML, atributo, JavaScript, URL). Los motores de plantillas modernos lo hacen por defecto: el problema llega cuando alguien lo desactiva.
- Si hay que admitir HTML del usuario, pasarlo por un sanitizador conocido en lugar de un filtro propio con listas negras.
- Una política de seguridad de contenido (CSP) estricta como segunda barrera.
- Cookies de sesión con
HttpOnlypara que el JavaScript no pueda leerlas.
Al reportarlo
Indica el tipo (reflejado, almacenado o DOM), el campo exacto y qué puede hacer un atacante en ese sitio concreto: no es lo mismo ejecutar código en una página estática que en el panel de administración.
Casos reales de XSS
Ver los 10 →XSS de un clic en Fizzy (Basecamp): paginación de Turbo y blobs HTML sin adjuntar para robar un token de escritura
Un atacante con cuenta propia conseguía ejecutar JavaScript en el origen de Fizzy cuando una víctima de otra cuenta abría una URL de tablero público, y con ello crear y exfiltrar un token de escritura de la víctima.
8 min
XSS reflejado en el asistente de IA de help.shopify.com mediante un saludo en Markdown inyectado por CSRF
Un formulario de otra web podía fijar el saludo del asistente de IA de help.shopify.com. Ese saludo se mostraba como Markdown, y un enlace javascript: ejecutaba código en la sesión de la víctima.
3 min
XSS almacenado en la exportación HTML de Rocket.Chat, inyectable sin cuenta desde el LiveChat
La exportación de salas a HTML de Rocket.Chat (y la descarga de datos personales) volcaba los mensajes sin escapar. Un visitante anónimo del LiveChat podía colar código que se ejecutaba al abrir el fichero exportado.
5 min
XSS almacenado en el editor Trix 2.1.16 de Basecamp: atributos serializados que se saltan DOMPurify
Trix 2.1.16 conservaba cualquier atributo data-trix- y, al serializar, aplicaba sin filtrar los atributos guardados en data-trix-serialized-attributes, lo que permitía colar manejadores como onerror en el HTML final.
4 min
XSS almacenado en el campo de servidores de nombres de los ajustes de cuenta de un servicio de Tucows
El campo de nameservers de los ajustes de cuenta guardaba HTML sin validar si se modificaba la petición PUT, y el código se ejecutaba al recargar la página. Solo afecta a la propia cuenta (self-XSS), por lo que la severidad es baja.
3 min
Rails ActionText: una etiqueta <action-text-markdown> esquiva la validación de esquemas URI en to_markdown
En ActionText, un texto enriquecido con una etiqueta <action-text-markdown> escrita por el usuario salía tal cual en la exportación a Markdown, lo que permitía colar enlaces javascript: o data: que la validación de URI debía bloquear.
3 min