Aller au contenu

Comment prévenir une attaque Host header injection

Comment prévenir une attaque Host header injection

Section intitulée « Comment prévenir une attaque Host header injection »

L’attaque par manipulation d’en-tête Host exploite une confiance mal placée : l’application traite une valeur fournie par le client comme l’adresse publique qu’elle doit servir. Le défaut permet de fabriquer des liens, contourner des contrôles d’origine ou empoisonner des caches avec un domaine contrôlé par l’attaquant. Ce n’est pas une curiosité de proxy, c’est une frontière de confiance mal définie.

→ Lire l’explication complète de la marmotte

HTTP (Hypertext Transfer Protocol) transporte l’en-tête Host pour indiquer le nom de site demandé. Sur une même adresse réseau, un proxy inverse peut ainsi choisir entre plusieurs applications. Depuis HTTP/1.1, cet en-tête est requis pour les requêtes d’origine. Avec HTTP/2 et HTTP/3, l’autorité est surtout portée par le pseudo-en-tête :authority, mais les passerelles peuvent le convertir ou l’aligner avec Host. La distinction compte : filtrer seulement l’un des deux laisse parfois une porte latérale, parce que les protocoles adorent les héritages mal rangés.

Le client contrôle Host. Cette règle ne change pas parce qu’une requête arrive après TLS (Transport Layer Security), via un équilibreur de charge, ou depuis une adresse interne. Le proxy peut décider qu’une valeur est acceptable, mais l’application ne doit jamais présumer que cette valeur est devenue fiable par magie. Un en-tête transmis est une donnée, pas un certificat de naissance.

Le compromis central est net. Accepter dynamiquement de nombreux noms d’hôtes facilite le multi-tenant, les environnements de prévisualisation et certains routages hérités. Le prix est une surface d’autorité qui varie avec l’entrée attaquante. La recommandation est de maintenir une liste explicite de noms publics autorisés, puis de définir un unique nom canonique pour chaque application. Le routage dynamique reste recevable uniquement si chaque nom est résolu dans un registre contrôlé, jamais dans la requête elle-même.

Une application vulnérable construit souvent une URL absolue :

https://{Host}/reset-password?token={jeton}

Si Host vaut evil.example, le courriel de réinitialisation pointe vers evil.example. Le jeton reste cryptographiquement valide, mais il est livré au mauvais domaine. Un jeton signé ne répare pas une destination empoisonnée. La cryptographie garantit l’intégrité du jeton, pas la loyauté de l’URL qui le transporte.

Le même défaut affecte les redirections, les balises canonical, les en-têtes Location, les politiques de sécurité, les liens d’invitation et certains mécanismes d’authentification fédérée. Dans les flux OAuth (Open Authorization), la comparaison de l’adresse de retour doit porter sur une valeur enregistrée à l’avance. La reconstruire depuis Host revient à laisser le navigateur choisir où terminer l’authentification. C’est généreux, au sens où laisser les clés sur la porte est généreux.

Démonstration contrôlée de Host header injection

Section intitulée « Démonstration contrôlée de Host header injection »

La démonstration suivante vise une instance de laboratoire ou une application dont l’exploitation est autorisée. Elle révèle un comportement vulnérable lorsque la réponse génère une URL absolue en reprenant l’hôte fourni. Aucun compte réel, aucun jeton réel, aucune messagerie de production ne doit intervenir.

Fenêtre de terminal
# Requête de test : remplacer app-lab.example par une cible de laboratoire autorisée.
# Le domaine injecté est volontairement fictif et ne doit pas être résolu.
curl -i \
-H 'Host: collecte-invalide.example' \
'https://app-lab.example/password/reset'
# Chercher dans la réponse les en-têtes Location, Link, Content-Location,
# les cookies Domain, les balises canonical ou toute URL absolue reflétant Host.

Un résultat vulnérable contient collecte-invalide.example dans une redirection, un document HTML (HyperText Markup Language), un lien de courriel prévisualisé ou un en-tête. Une réponse 400 Bad Request, 421 Misdirected Request ou une redirection vers le domaine canonique constitue le comportement attendu, sous réserve que cette décision soit prise avant l’application métier.

Tester aussi les variantes qui traversent réellement l’infrastructure : port explicite (Host: collecte-invalide.example:8443), casse, point terminal, nom internationalisé si l’environnement l’autorise, adresse IP et requête HTTP/2 avec :authority. L’objectif n’est pas de collectionner des cas bizarres pour un tableur héroïque : chaque couche doit aboutir à la même décision d’autorisation.

Le test doit observer la requête au proxy et à l’application. Une différence entre les deux est un signal d’alerte. Le proxy peut sélectionner le bon virtual host tout en transmettant à l’application le Host original. Inversement, une application peut faire confiance à X-Forwarded-Host, en-tête souvent ajouté par un proxy, alors que ce dernier accepte aussi une valeur client préexistante. La règle est stricte : supprimer les en-têtes de transfert entrants, puis les reconstruire au proxy de confiance.

Les attaques de désynchronisation HTTP aggravent ce défaut. Lorsque le frontal et le serveur applicatif ne découpent pas une requête de la même manière, une valeur Host peut atteindre une requête différente de celle inspectée par le frontal. Les protections contre la désynchronisation et celles contre l’injection Host sont donc liées : rejet des requêtes ambiguës, normalisation unique, versions maintenues et absence de doubles parseurs inventés entre deux cafés.

Pour classer les effets et leurs prérequis, la page des failles applicatives donne le cadre de lecture. Les échanges réseau doivent aussi être inspectés avec une méthode multi-couche, pas avec la foi placée dans un seul journal.

Validation d’hôte au proxy et dans l’application

Section intitulée « Validation d’hôte au proxy et dans l’application »

La défense primaire se place au point d’entrée : le proxy inverse accepte une liste finie de noms, rejette le reste, puis transmet à l’application une valeur canonique contrôlée. Un site par défaut qui sert l’application principale à tout nom inconnu est vulnérable par conception. Le site par défaut doit retourner une erreur sans transférer la requête.

Exemple Nginx, avec une application servie exclusivement sous deux noms publics :

# Rejeter tout nom d'hôte non déclaré avant le proxy applicatif.
server {
listen 443 ssl default_server;
server_name _;
return 444;
}
# N'accepter que les noms publiés, puis imposer l'autorité canonique.
server {
listen 443 ssl;
server_name portail.example portail-secours.example;
location / {
proxy_set_header Host portail.example;
proxy_set_header X-Forwarded-Host portail.example;
proxy_set_header X-Forwarded-Proto https;
proxy_pass http://application_interne;
}
}

Le code 444 est propre à Nginx et ferme la connexion sans réponse HTTP. Un 400 explicite facilite parfois le diagnostic et les métriques. Le choix dépend de l’exploitation, pas de la sécurité fondamentale : l’exigence est de ne jamais atteindre l’application avec un hôte non validé.

L’application doit conserver une seconde validation. Cette redondance protège les accès directs, les changements de proxy et les environnements de test qui contournent le frontal, ce qui finit toujours par arriver sous couvert de dépannage urgent. Configurer un paramètre de type allowed_hosts, trusted_hosts ou équivalent avec des noms exacts. Ne pas accepter *.example sans nécessité démontrée : un sous-domaine oublié, abandonné ou délégué devient alors une autorité implicite.

Les URL externes ne doivent pas être construites à partir de la requête. Définir une base publique par environnement, par exemple https://portail.example, dans la configuration de déploiement. Pour un produit multi-tenant, associer le tenant à un identifiant interne résolu après validation du nom, puis choisir l’URL canonique depuis le registre de tenants. Le nom reçu sert à sélectionner, jamais à composer directement une sortie.

Les contrôles de même origine méritent la même rigueur. Origin et Referer peuvent être absents selon le flux, mais lorsqu’ils sont contrôlés, leur comparaison doit viser un ensemble enregistré de schémas, noms et ports. Host ne prouve pas l’origine d’une requête : il indique seulement ce que le client prétend appeler. Pour les sujets adjacents de fédération et de sessions, la rubrique identités évite de confondre identité, origine et destination.

L’empoisonnement de cache est l’impact le plus discret. Si un cache partage une réponse dont la clé ne contient pas Host, ou si l’application incorpore Host dans le contenu mais que le cache ne le varie pas, une réponse construite avec le domaine attaquant peut être servie à des victimes. Les balises canonical, les scripts externes et les redirections sont des cibles classiques. Le cache ne crée pas la vulnérabilité, il lui donne une mémoire et parfois un mégaphone.

La confusion entre nom d’hôte et couche TLS produit un faux sentiment de sécurité. Le nom indiqué lors de la négociation TLS peut sélectionner un certificat et un virtual host, mais il ne valide pas automatiquement le Host HTTP ultérieur. Les deux doivent être cohérents et contrôlés. Une configuration où le certificat couvre plusieurs noms reste légitime, à condition que chacun soit explicitement autorisé pour l’application concernée. Les détails de certificats et de terminaison TLS sont traités dans PKI et TLS.

L’attaque peut aussi mener à une fuite de jeton par journalisation. Beaucoup de systèmes enregistrent une URL complète dans les traces d’accès, les outils de suivi ou les aperçus de courriel. Une redirection vers un domaine malveillant emporte parfois un secret dans la chaîne de requête. La contre-mesure est double : ne pas mettre de secret durable dans une URL, et réduire la durée de vie des jetons à quelques minutes avec usage unique. Un jeton de réinitialisation ne mérite pas une carrière de cookie.

Ce mécanisme ne garantit pas l’absence de compromission de domaine, de phishing, de vol de session, de contournement de DNS (Domain Name System), ni de défaut dans la logique métier. Une liste d’hôtes autorisés ne rend pas une redirection ouverte acceptable, ne protège pas un compte compromis et ne remplace pas l’authentification forte. Chaque contrôle a son travail, ce qui déçoit les produits qui promettent de sécuriser toute la planète depuis un bouton bleu.

La liste des hôtes autorisés est une donnée de sécurité. Elle doit appartenir à la configuration versionnée, être revue avec chaque ajout de domaine et être cohérente entre pare-feu applicatif, proxy, application, générateur de courriels et fournisseur d’identité. La dérive entre ces listes produit soit une indisponibilité, soit un contournement. Les deux sont très mal reçus, mais pas par les mêmes équipes.

Journaliser les rejets d’hôte avec la valeur normalisée, l’adresse source et le point d’entrée, sans recopier de jeton ni de cookie. Déclencher une alerte sur les rafales de noms aléatoires, les hôtes contenant des caractères inattendus et les divergences entre Host, :authority et en-têtes transférés. Une hausse de rejets après un déploiement peut signaler un domaine manquant ; une hausse continue depuis Internet signale généralement une reconnaissance automatisée.

Tester la configuration à chaque changement de proxy, de bibliothèque HTTP ou de domaine public. Les tests doivent couvrir le rejet d’hôtes inconnus, la génération d’URL absolues, les redirections et les flux de récupération de compte. Sauvegarder la configuration de proxy et le registre des noms autorisés avec le reste de l’infrastructure. La reprise consiste à restaurer une politique cohérente, pas à remettre un ancien fichier au hasard et espérer que les hôtes se comportent avec dignité.

Sans liste d’hôtes contrôlée, le courriel de réinitialisation piégé part du serveur de messagerie légitime : il passe les contrôles SPF, DKIM et DMARC, et les filtres anti-hameçonnage n’ont aucun motif de le signaler, puisque seul le lien diffère. Un proxy qui route selon Host sans liste finie peut aussi être dirigé vers un virtual host interne (console d’administration, service de métriques, nom comme localhost ou adresse privée) : la requête atteint alors un service jamais exposé, forme de SSRF par routage. Les contrôles d’accès qui comparent Host à un nom interne pour réserver une page d’administration aux appels locaux deviennent contournables par une simple valeur d’en-tête. Enfin, les journaux attribuent des requêtes à des domaines inventés, ce qui fausse l’analyse d’incident.

Un navigateur envoie une requête à un site avec un champ qui indique le nom du site demandé. Ce champ aide un serveur qui héberge plusieurs sites à choisir la bonne application. Le problème apparaît quand l’application reprend ce nom sans le vérifier pour fabriquer une adresse complète, par exemple dans un message de réinitialisation de mot de passe ou dans une redirection.

Un attaquant peut alors envoyer une requête contenant son propre nom de domaine dans ce champ. Si le serveur le recopie, il produit un lien qui semble venir du service mais qui mène vers le domaine de l’attaquant. Une personne qui ouvre ce lien peut transmettre un code temporaire, un jeton de connexion ou d’autres informations au mauvais endroit. Certains systèmes de stockage temporaire peuvent aussi conserver cette mauvaise réponse et la montrer ensuite à d’autres personnes.

La protection commence au serveur placé devant l’application. Il doit accepter seulement les noms déclarés et rejeter tous les autres. Ensuite, il doit transmettre à l’application un nom fixé dans sa configuration. L’application vérifie aussi ce nom, car un accès direct ou une modification d’infrastructure peut contourner la première vérification.

Les liens envoyés vers l’extérieur doivent venir d’une adresse enregistrée dans la configuration, jamais de la requête reçue. Si un nom inconnu arrive, la requête doit échouer avant toute action métier. Les journaux doivent montrer ces rejets, sans enregistrer de mots de passe, de cookies ou de codes secrets.

À lire aussi

Pages voisines

: les deux pages se citent l’une l’autre.