Saltar al contenido
Categoria: Pentest9 min de lectura

XSS Moderno: DOM, Stored y Reflected con Ejemplos Reales en Entorno de Pruebas

Por Lucas Andrade ·

Los tres sabores de XSS diseccionados en sandbox con payloads, flujo de explotacion y mitigaciones mediante CSP estricta, Trusted Types y sanitizacion con DOMPurify.

XSS Moderno: DOM, Stored y Reflected con Ejemplos Reales en Entorno de Pruebas

En 2025 el informe de HackerOne registro XSS como el segundo bug mas reportado en programas publicos, con mediana de USD 750 por hallazgo y picos de USD 20 mil en objetivos enterprise. La familia sigue viva porque los navegadores evolucionaron, pero los pipelines de frontend todavia concatenan strings en innerHTML sin ceremonia. El equipo Basilisk abre un laboratorio con tres aplicaciones intencionalmente vulnerables, registra peticiones en Burp Suite y muestra cada vector con payload, contexto y parche. Antes de avanzar, asegura que tu lab este aislado, porque ejecutar payloads contra terceros sin autorizacion escrita sigue siendo delito en la mayoria de jurisdicciones.

Las tres familias de XSS de un vistazo

XSS es la inyeccion y ejecucion de JavaScript del atacante en el contexto de seguridad de un origen ajeno. Tres familias cubren practicamente todos los casos: Reflected (el payload llega en la peticion y se refleja de inmediato), Stored (el payload persiste en el backend y alcanza a cada visitante futuro) y DOM-based (la inyeccion ocurre por completo del lado cliente, sin que el servidor vea el payload). El impacto es el mismo en los tres: el atacante hereda los privilegios de la victima en el navegador, puede robar tokens de sesion, actuar en nombre del usuario y, contra un admin, tomar toda la aplicacion.

Reflected XSS

Reflected XSS aparece cuando la entrada del usuario regresa en la respuesta HTTP sin codificacion adecuada, generalmente via querystring o formulario GET. En nuestro lab una busqueda en /search?q= concatena el termino dentro de un h2, asi que el payload clasico <svg/onload=alert(document.domain)> dispara en el contexto top-level. El detalle que separa un reporte amateur de uno profesional es probar impacto: robar la cookie de sesion via fetch a un dominio del atacante solo funciona si la cookie no es HttpOnly. Documenta la brecha con captura en Burp, con la peticion exacta y el contexto de respuesta, porque un reporte sin peticion reproducible cae en la pila de duplicados o informativos.

Stored XSS

Stored XSS es el mas peligroso porque persiste en la base y alcanza a cualquier visitante. En DVWA fijamos el nivel medium, enviamos en el campo de comentario el payload <img src=x onerror=...> que codifica la cookie en base64 y la envia a un webhook, y observamos al webhook recibir sesiones de moderadores en segundos. En apps reales la superficie incluye renderers de Markdown, plantillas de email, exports CSV abiertos en Excel e incluso metadatos EXIF leidos por un dashboard interno. La unica defensa robusta combina sanitizacion en el input con escape en el output; uno sin el otro es teatro, porque un valor guardado limpio dispara igual al caer en el contexto equivocado.

DOM-based XSS

DOM-based XSS ocurre enteramente en el cliente, sin que el payload toque el servidor. El clasico location.hash canalizado a document.write aun viaja en widgets de chat legacy y en apps React que entregan datos del hash a dangerouslySetInnerHTML. Adaptamos el nivel 1 del Google XSS Game dentro del lab y demostramos la caza de sinks abriendo DevTools, activando 'Pause on exceptions' y desatando la extension DOM Invader de Burp. El flujo de triage siempre es el mismo: ubicar la fuente (hash, search, postMessage), rastrearla hasta el sink (innerHTML, eval, setAttribute), validar con payload minimo, luego escalar. Como el servidor no ve nada, las WAF del lado servidor son ciegas aqui.

El contexto lo es todo: escape correcto

El mismo payload dispara o falla segun el contexto de inyeccion. El cuerpo HTML necesita codificacion de entidades HTML, un valor de atributo agrega manejo de comillas, un bloque <script> necesita escape de string JavaScript, una URL necesita codificacion consciente del contexto, y un contexto de estilo tiene reglas propias. La raiz de casi todo XSS es un valor que cruza de un contexto a otro sin recodificarse. Por eso la regla de OWASP es: codifica en la salida, acorde al contexto, nunca filtres de forma generica en la entrada. Un filtro de lista negra que solo quita angulos cae al instante ante vectores de atributo como onmouseover.

Montar el laboratorio

Monta hoy el laboratorio con DVWA, OWASP Juice Shop y una app Next.js reducida, todo en contenedores Docker en una red aislada sin ruta a internet salvo un webhook controlado para la prueba de exfiltracion. Enruta todo el trafico del navegador por Burp, activa el logging del historial de proxy y arma una coleccion en Repeater por vector. Reproduce los tres vectores hasta que cada uno logre un popup, y anota para cada uno la peticion exacta, el contexto de inyeccion y el fragmento de respuesta. Este runbook es luego la columna de reportes de bug bounty limpios con reproduccion en una frase.

Mitigacion moderna: CSP, Trusted Types, DOMPurify

La mitigacion moderna dejo de tratarse de filtrar angulos hace anos. Content Security Policy nivel 3 con nonce por peticion mata la inyeccion inline incluso cuando el atacante mete HTML en la pagina, siempre que no cedas y agregues unsafe-inline como fallback. Trusted Types convierte las asignaciones a innerHTML en un type error salvo que pasen por una policy registrada. Combinado con DOMPurify para HTML rico, las mediciones de Google muestran cerca de 90% de reduccion de superficie. HttpOnly en la cookie de sesion neutraliza el robo clasico de cookie, y SameSite amortigua el reenvio del credencial a origenes ajenos.

Shift-left: cazar XSS en CI

Cablea la deteccion en CI corriendo Semgrep con javascript.lang.security.audit.xss en cada PR, complementado con plugins de ESLint que marcan dangerouslySetInnerHTML y asignaciones crudas a innerHTML. Una pasada DAST contra la instancia de staging atrapa lo que el analisis estatico pierde, como parametros reflejados en plantillas de terceros. La clave es que un sink nuevo rompa el build, no que solo genere un reporte que nadie lee. Asi la prevencion de XSS se vuelve parte de la Definition of Done en lugar de un pentest tardio.

Errores comunes

El primer error es la lista negra: cualquier filtro que solo bloquee payloads conocidos cae ante variantes de codificacion y manejadores de eventos alternativos. El segundo es tratar la WAF como unica defensa, evadida con mezcla de mayusculas, comentarios y doble codificacion. El tercero es una CSP con unsafe-inline, que en la practica no bloquea nada. El cuarto es asumir que un framework como React es seguro por defecto mientras dangerouslySetInnerHTML y la inyeccion en href siguen abiertos. El quinto es la falta de HttpOnly, que eleva cada brecha reflejada directo a robo de sesion.

Checklist

Prueba cada punto de inyeccion en los cinco contextos (cuerpo HTML, atributo, script, URL, estilo). Confirma si la cookie de sesion tiene HttpOnly y SameSite. Verifica la CSP: sin unsafe-inline, nonce por peticion, script-src restrictivo. Prueba si Trusted Types esta forzado. Prueba el impacto con una demostracion inofensiva pero inequivoca (un popup con document.domain o un callback sin datos reales). Documenta reproduccion en una frase, impacto y parche sugerido por hallazgo, y mapea cada hallazgo a los buckets STRIDE de Tampering y Elevation antes de abrir el ticket.

Vectores avanzados mas alla del popup

Un alert(1) prueba la inyeccion, pero un reporte maduro muestra la cadena detras. Con un XSS estable puedes leer tokens CSRF directo del DOM y emitir peticiones en nombre de la victima, derrotando la defensa CSRF habitual porque la peticion nace del origen legitimo. A traves de la API Fetch el payload puede consultar endpoints autenticados y exfiltrar respuestas, incluidos datos de perfil o paneles de admin. Un service worker registrado via XSS sobrevive incluso a la recarga de la pagina e intercepta trafico futuro. Especialmente subestimada es la combinacion de XSS y un handler postMessage abierto que acepta datos a traves de fronteras de frame: un solo chequeo de origen faltante convierte una subdominio inofensiva en una cabeza de playa. Por eso nunca valoramos el impacto por el popup, sino por la accion realmente alcanzable: toma de cuenta, exfiltracion de datos o escalada de privilegio a un admin, cada una con una prueba demostrada pero no danina.

Reporte y divulgacion responsable

Un hallazgo tecnicamente correcto sin reporte limpio se desvanece. La estructura que se tria en HackerOne, Bugcrowd e Intigriti siempre es la misma: un resumen preciso, la URL y el parametro afectados, reproduccion en una frase con el payload exacto, un impacto demostrado y una sugerencia concreta de parche. La prueba de impacto se mantiene deliberadamente inofensiva: un popup con document.domain o un callback sin datos reales de usuario, nunca una exfiltracion masiva de sesiones vivas. Capturas y un clip corto aceleran mucho la triage. Cinete estricto al scope del programa; un acierto fuera de scope no es gloria, es riesgo legal. Documenta la justificacion del vector CVSS para que la severidad no parezca negociable. Un reporte que hace el trabajo del defensor de entender y desplegar el fix se paga mas rapido y construye la reputacion que luego lleva a invitaciones privadas con bounties mas altos.

FAQ: Un framework moderno hace imposible el XSS?

No. React, Angular y Vue escapan bindings estandar de forma automatica, pero dejan escotillas deliberadas abiertas: dangerouslySetInnerHTML, v-html, bypassSecurityTrustHtml y atributos href/src con URLs javascript:. Esas escotillas son la fuente mas comun de XSS en bases modernas. El framework reduce la superficie pero no reemplaza CSP, Trusted Types y escape consciente del contexto.

FAQ: Basta una WAF contra XSS?

No. Una WAF es una capa externa util, pero es evadible con codificacion, mezcla de mayusculas y trucos a nivel de protocolo, y no ve el XSS DOM-based porque el payload nunca llega al servidor. Trata la WAF como alerta temprana y reduccion de ruido, nunca como reemplazo del escape en la salida y una CSP estricta.

Conclusion

Takeaway practico: monta el laboratorio, reproduce los tres vectores hasta lograr un popup en cada uno, luego agrega CSP con nonce, Trusted Types y DOMPurify y repite los mismos payloads. Lo que aun dispare merece un ajuste de policy hasta que se rompa. Registra cada iteracion en un runbook personal porque los mejores reportes de XSS exigen reproduccion en una frase, impacto demostrado y parche sugerido. XSS no es un problema resuelto, es una disciplina: codifica por contexto, apila defensa en profundidad y trata cada nueva salida como sink potencial hasta demostrar lo contrario.

Related posts

Nenhum comentário ainda

Seja o primeiro a comentar.

Deixe seu comentário

Entre com sua conta Canverly para comentar. Você pode usar a mesma conta em qualquer site da rede.

Entrar com Canverly