HTTP : contourner un filtrage d'adresse IP
Certaines pages web ou fonctionnalités d’administration sont restreintes par filtrage d’adresse IP. Quand cette vérification repose sur des en-têtes HTTP contrôlables par le client plutôt que sur l’IP de connexion TCP, il est possible d’usurper l’adresse source et de contourner la restriction.
Les plages d’adresses privées (RFC 1918)
Section titled “Les plages d’adresses privées (RFC 1918)”La RFC 1918 définit trois blocs d’adresses réservés aux réseaux privés. Ces adresses ne sont pas routables sur Internet : un paquet portant une de ces adresses comme source ou destination ne franchit pas la frontière d’un réseau d’entreprise.
| Bloc | Plage | Nombre d’adresses |
|---|---|---|
10.0.0.0/8 | 10.0.0.0 – 10.255.255.255 | ~16,7 millions |
172.16.0.0/12 | 172.16.0.0 – 172.31.255.255 | ~1 million |
192.168.0.0/16 | 192.168.0.0 – 192.168.255.255 | ~65 536 |
Un serveur qui restreint l’accès aux adresses de ces plages suppose que seuls des clients internes (sur le réseau local) peuvent envoyer des requêtes avec ces adresses. C’est faux si le filtrage repose sur des en-têtes HTTP.
L’en-tête X-Forwarded-For
Section titled “L’en-tête X-Forwarded-For”X-Forwarded-For est un en-tête HTTP non standard, mais largement utilisé par les proxys et load balancers pour transmettre l’adresse IP d’origine du client à travers une chaîne d’intermédiaires.
Format :
X-Forwarded-For: <client>, <proxy1>, <proxy2>L’adresse la plus à gauche est censée être celle du client d’origine. Chaque proxy ajoute la suivante à droite.
Exemples légitimes :
X-Forwarded-For: 203.0.113.45X-Forwarded-For: 203.0.113.45, 198.51.100.10, 198.51.100.22Le vecteur d’attaque
Section titled “Le vecteur d’attaque”Si le serveur lit X-Forwarded-For pour effectuer son contrôle d’accès sans valider que la valeur provient d’un proxy de confiance, n’importe quel client peut forger cet en-tête.
GET /admin HTTP/1.1Host: exemple.comX-Forwarded-For: 127.0.0.1GET /admin HTTP/1.1Host: exemple.comX-Forwarded-For: 192.168.1.1Si le code serveur ressemble à :
# Vérification naïve — contournableclient_ip = request.headers.get('X-Forwarded-For', request.remote_addr)if client_ip not in ALLOWED_IPS: abort(403)Forger l’en-tête suffit à passer le contrôle.
En-têtes à tester
Section titled “En-têtes à tester”Plusieurs en-têtes peuvent être lus par le serveur selon la configuration du proxy ou du framework :
X-Forwarded-For: 127.0.0.1X-Forwarded-For: 192.168.0.1X-Real-IP: 127.0.0.1X-Real-IP: 10.0.0.1X-Client-IP: 127.0.0.1Forwarded: for=127.0.0.1Forwarded: for="[::1]"True-Client-IP: 127.0.0.1CF-Connecting-IP: 127.0.0.1Les adresses à tester en priorité :
127.0.0.1 # loopback — "la requête vient du serveur lui-même"::1 # loopback IPv610.0.0.1 # plage privée RFC 1918172.16.0.1 # plage privée RFC 1918192.168.1.1 # plage privée RFC 1918Tester avec curl
Section titled “Tester avec curl”# Tester X-Forwarded-For avec une IP localecurl -H "X-Forwarded-For: 127.0.0.1" https://exemple.com/admin
# Tester X-Real-IPcurl -H "X-Real-IP: 192.168.0.1" https://exemple.com/admin
# Tester l'en-tête standardisé Forwarded (RFC 7239)curl -H "Forwarded: for=127.0.0.1" https://exemple.com/admin
# Combiner plusieurs en-têtescurl \ -H "X-Forwarded-For: 127.0.0.1" \ -H "X-Real-IP: 127.0.0.1" \ -H "X-Client-IP: 127.0.0.1" \ https://exemple.com/adminUn code de réponse qui change (200 au lieu de 403) confirme que le filtrage repose sur ces en-têtes.
Remédiation
Section titled “Remédiation”Ne pas utiliser X-Forwarded-For pour les contrôles de sécurité
Section titled “Ne pas utiliser X-Forwarded-For pour les contrôles de sécurité”L’IP de connexion TCP (REMOTE_ADDR en PHP, request.remote_addr en Python, req.socket.remoteAddress en Node.js) est la seule valeur non falsifiable côté client. Elle reflète l’IP qui a établi la connexion réseau.
# Correct : utiliser l'IP de connexion TCPclient_ip = request.remote_addrif client_ip not in ALLOWED_IPS: abort(403)Si un reverse proxy est nécessaire
Section titled “Si un reverse proxy est nécessaire”Quand l’application est derrière un reverse proxy de confiance (Nginx, AWS ALB…), deux approches valides :
Méthode par comptage : avec un seul proxy connu, lire le Nième élément en partant de la droite dans X-Forwarded-For, où N est le nombre de proxys de confiance.
# Avec exactement 1 proxy de confianceforwarded_for = request.headers.get('X-Forwarded-For', '')ips = [ip.strip() for ip in forwarded_for.split(',')]# L'IP cliente est la dernière ajoutée par le proxy de confianceclient_ip = ips[-(1 + nombre_proxys_de_confiance)]Méthode par liste : parcourir X-Forwarded-For de droite à gauche, ignorer les IPs correspondant à des proxys connus, prendre la première inconnue.
Ne jamais lire le premier élément (le plus à gauche) : c’est celui que le client contrôle entièrement.
Configurer le filtrage côté infrastructure
Section titled “Configurer le filtrage côté infrastructure”Le filtrage IP a plus de sens au niveau du réseau (pare-feu, règles de sécurité groupe AWS/GCP, nginx allow/deny) qu’au niveau applicatif, précisément parce qu’il ne dépend alors pas d’en-têtes HTTP :
location /admin { allow 10.0.0.0/8; allow 192.168.0.0/16; deny all;}À retenir
Section titled “À retenir”Un filtrage IP basé sur X-Forwarded-For ou tout autre en-tête HTTP modifiable par le client n’est pas un contrôle d’accès. La seule source d’IP fiable est l’adresse de connexion TCP. Tout le reste est une valeur déclarative que le client choisit lui-même.