Contourner un contrôle proxy par confusion de chemin
Section intitulée « Contourner un contrôle proxy par confusion de chemin »Une confusion de chemin apparaît lorsque le reverse proxy et le backend ne donnent pas le même sens à une même adresse HTTP (Hypertext Transfer Protocol). Le proxy autorise selon une représentation, l’application sert selon une autre, et le contrôle d’accès devient un décor de théâtre avec serrure peinte.
Le problème ressemble à un gardien qui laisse passer une personne parce que son billet semble viser une zone publique, alors que le responsable à l’intérieur lit ce même billet autrement et ouvre une salle réservée. Le désaccord porte sur l’étiquette, pas sur l’identité de la personne.
→ Lire l’explication complète de la marmotte
Normalisation de chemin et frontière d’autorisation
Section intitulée « Normalisation de chemin et frontière d’autorisation »Un chemin HTTP est la partie d’une URI (Uniform Resource Identifier) qui suit le nom de domaine, par exemple /admin/reports. Avant de comparer ce chemin à une règle, un composant peut le normaliser : décoder les caractères encodés avec %, supprimer les segments . et .., fusionner des barres obliques répétées, retirer des paramètres de segment comme ;jsessionid=..., ou convertir certains caractères Unicode.
Le problème n’est pas la normalisation en soi. Le problème est d’autoriser sur une forme et de router sur une autre. Une politique qui dit « /public/ est accessible sans authentification » doit être évaluée sur exactement la même chaîne que celle utilisée pour sélectionner le contrôleur applicatif. Sinon, /public/%2e%2e/admin peut être vu comme public par le proxy, puis comme /admin par le backend après décodage de %2e en point et résolution de ...
Les divergences les plus fréquentes concernent :
- le décodage unique ou répété de
%2f(barre oblique),%5c(antislash),%2e(point) et%00(octet nul) ; - la résolution des segments
.et..avant ou après décodage ; - la conservation ou la fusion de
//; - le traitement de
\comme séparateur par certains frameworks exécutés sur des plateformes Windows ; - les paramètres de segment, par exemple
/admin;foo=bar/metrics; - les chemins Unicode visuellement proches, dont la comparaison dépend de la normalisation appliquée.
Le compromis central est net : conserver l’URI brute facilite le diagnostic et certains routages transparents, mais interdit de fonder une décision de sécurité sur elle. Normaliser tôt réduit les ambiguïtés, mais peut casser des applications qui considèrent les caractères encodés comme des données métier. La recommandation est de trancher en faveur de la sécurité : rejeter les chemins ambigus à la frontière plutôt que tenter de préserver toutes les interprétations exotiques. Une API qui exige %2F dans un identifiant a besoin d’une conception explicite, pas d’une exception glissée dans une expression régulière un vendredi soir.
Un reverse proxy ne remplace pas l’autorisation applicative. Il peut segmenter des surfaces, imposer une authentification centralisée ou couper des routes interdites, mais il ne garantit ni l’autorisation objet, ni l’isolation entre locataires, ni l’absence de défaut dans le backend. Les règles de filtrage réseau restent complémentaires, comme détaillé dans la segmentation et le filtrage.
Démonstration reproductible avec NGINX et backend
Section intitulée « Démonstration reproductible avec NGINX et backend »Le scénario suivant montre une erreur de conception volontaire : NGINX décide que toute URI brute commençant par /public/ est anonyme, puis transmet cette URI à un backend qui décode et résout les segments. Le backend protège /admin uniquement si l’en-tête transmis par le proxy indique une authentification.
Prérequis : Docker Engine, Docker Compose v2, NGINX 1.25 ou supérieur et Python 3.12 dans l’image backend. L’exemple est local, sans Transport Layer Security (TLS), car ajouter un certificat à une faille de routage ne fait que chiffrer le mauvais chemin avec beaucoup de conviction.
services: proxy: image: nginx:1.25-alpine ports: - "8080:80" volumes: - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro depends_on: - app
app: image: python:3.12-alpine command: > sh -c "cat > /app.py <<'PY' from http.server import BaseHTTPRequestHandler, HTTPServer from urllib.parse import unquote import posixpath
class Handler(BaseHTTPRequestHandler): def do_GET(self): # Le backend décode puis résout les segments de chemin. path = posixpath.normpath(unquote(self.path.split('?', 1)[0])) if path == '/admin': if self.headers.get('X-Authenticated') != 'true': self.send_response(403); self.end_headers() self.wfile.write(b'admin interdit\\n'); return body = b'secret admin\\n' else: body = ('route: ' + path + '\\n').encode() self.send_response(200); self.end_headers(); self.wfile.write(body)
HTTPServer(('0.0.0.0', 8000), Handler).serve_forever() PY python /app.py"server { listen 80;
location / { # ERREUR : $request_uri est l'URI brute, non normalisée. # Toute chaîne qui commence par /public/ reçoit un passe-droit. if ($request_uri ~ ^/public/) { proxy_set_header X-Authenticated true; }
proxy_pass http://app:8000; }}Démarrer avec docker compose up, puis exécuter les requêtes suivantes :
# Route administrative directe : refusée.curl -i http://localhost:8080/admin
# Contournement : le proxy voit /public/, le backend sert /admin.curl -i http://localhost:8080/public/%2e%2e/adminLe second appel doit retourner secret admin. Le comportement dépend ici explicitement du backend de démonstration, donc il est déterministe. En production, une variante équivalente survient lorsqu’un pare-feu applicatif, une règle d’authentification externe ou une ACL (Access Control List) compare $request_uri, tandis que le framework route sur une valeur déjà décodée.
Tester uniquement /../admin ne suffit pas. Les clients HTTP, les bibliothèques et NGINX peuvent eux-mêmes nettoyer cette forme avant l’envoi. Les cas à couvrir incluent au minimum %2e, %2f, %5c, doubles encodages comme %252e, doubles barres, paramètres ;, encodages Unicode et chemins terminés par un point ou une barre.
Surface d’attaque des routes protégées
Section intitulée « Surface d’attaque des routes protégées »L’attaquant n’a pas besoin d’exécuter du code sur le proxy. Il suffit de connaître ou deviner une route protégée, puis de chercher une représentation qui passe la première décision et change de sens à la seconde. Les journaux compliquent ensuite l’enquête : le proxy peut enregistrer /public/%2e%2e/admin, tandis que l’application journalise /admin. Sans identifiant de requête commun, corréler les deux devient un exercice de spéléologie textuelle.
Les cibles typiques sont les consoles d’administration, les points de terminaison de métriques, les interfaces de débogage, les téléchargements internes et les routes réservées à un réseau source. Une protection par préfixe est particulièrement exposée : /api/public/ contre /api/private/, ou /assets/ contre /assets-admin/, selon la sémantique exacte du moteur de correspondance.
Une règle de préfixe doit aussi définir sa frontière. Autoriser /api/public sans exiger une barre finale peut couvrir /api/publication ou /api/public-admin. La comparaison par segment est obligatoire : le préfixe autorisé est /api/public/, et non une sous-chaîne opportuniste. Les expressions régulières improvisées pour résoudre ce sujet ont l’élégance habituelle des correctifs à base de ruban adhésif : elles tiennent jusqu’au prochain caractère encodé.
Les protections d’identité ne corrigent pas ce défaut si elles sont attachées à la mauvaise route. Une fédération d’identité peut authentifier parfaitement un utilisateur, puis laisser le proxy attribuer le mauvais niveau d’accès. Les architectures d’accès doivent donc distinguer authentification, autorisation de route et autorisation sur la ressource. Les sujets IAM (Identity and Access Management) sont traités dans la rubrique identités.
Contre-mesures de normalisation cohérente
Section intitulée « Contre-mesures de normalisation cohérente »La mesure prioritaire consiste à définir une représentation canonique du chemin, puis à l’utiliser pour toute décision de sécurité et pour le routage. Dans une architecture proxy-backend, cette représentation doit être documentée et testée comme un contrat d’interface, au même titre que les en-têtes d’identité.
Recommandation opérationnelle :
- Rejeter à la frontière les caractères encodés qui modifient la structure du chemin :
%2f,%2F,%5c,%5C, ainsi que les doubles encodages pouvant les reconstituer. - Rejeter les segments
.et.., encodés ou non, après un décodage strict unique. - Désactiver les comportements de compatibilité non nécessaires : barres répétées, paramètres de segment et antislash comme séparateur.
- Évaluer les ACL sur le chemin normalisé fourni par le même composant qui route la requête.
- Conserver l’URI brute seulement comme donnée de journalisation, jamais comme vérité d’autorisation.
- Faire appliquer l’autorisation métier par le backend, même si le proxy bloque déjà les routes interdites.
NGINX normalise des éléments d’URI pour sélectionner une location, mais sa variable $request_uri conserve la requête brute. Mélanger les deux dans une logique d’accès crée précisément le fossé exploité ci-dessus. Éviter aussi les directives if pour construire une politique d’autorisation de chemin. Elles ne sont pas intrinsèquement maudites, mais une politique de sécurité disséminée dans des conditions de réécriture devient vite une chasse au trésor sans trésor.
Les tests doivent s’exécuter dans la chaîne complète : client, pare-feu applicatif, proxy, passerelle d’API (Application Programming Interface) et backend. Un test unitaire du contrôleur ne détecte pas une divergence au proxy. La suite de régression doit vérifier, pour chaque route protégée, que toutes les variantes ambiguës retournent 400 Bad Request ou 403 Forbidden, jamais une redirection vers la ressource visée.
Exploitation, supervision et reprise
Section intitulée « Exploitation, supervision et reprise »Les règles de normalisation sont du code de sécurité. Elles exigent revue, gestion de version, déploiement atomique et retour arrière testé. Modifier un proxy pour refuser %2F peut interrompre une application mal conçue qui encode des identifiants dans le chemin. Cette interruption est préférable à une autorisation indéfinissable, mais elle doit être détectée par des métriques de réponses 400 par route et par client.
Journaliser simultanément la cible normalisée, l’URI brute sous forme échappée, le code de réponse, la route de proxy sélectionnée et un identifiant de corrélation. Limiter l’accès à ces journaux : une URI peut contenir des jetons, des adresses électroniques ou des identifiants. Ne jamais placer de secret dans un chemin, car il traverse historiques, caches, traces et journaux avec l’enthousiasme d’un confetti dans une soufflerie.
Aucune clé cryptographique n’est nécessaire pour corriger la confusion de chemin, mais les secrets de session et les jetons portés par les en-têtes restent exposés si la faille ouvre une interface administrative. Prévoir leur révocation lorsque l’exposition est confirmée. Restaurer une configuration de proxy connue ne révoque pas les jetons déjà copiés, pas plus qu’un parapluie fermé ne sèche le sol.
Les sondes de supervision doivent utiliser des requêtes négatives : appeler périodiquement une variante encodée d’une route d’administration et exiger un refus. Une sonde qui ne vérifie que 200 OK sur la page d’accueil surveille la disponibilité, pas la frontière d’accès. Pour une lecture plus large des défauts applicatifs, consulter les failles documentées.
🐝 Ce qui arrive si on s’en passe
Section intitulée « 🐝 Ce qui arrive si on s’en passe »Sans représentation cohérente du chemin entre le reverse proxy et le backend, le contrôle d’accès devient un décor. Le proxy autorise selon une lecture de l’adresse, l’application sert selon une autre, et l’attaquant exploite ce désaccord pour atteindre une ressource réservée avec une requête que le proxy croyait viser une zone publique. La conséquence directe est un contournement d’autorisation : des routes d’administration ou des données protégées deviennent accessibles alors que la règle de filtrage semblait les couvrir. Le gardien laisse passer parce qu’il lit l’étiquette autrement que le service intérieur, sans que l’identité de l’appelant soit en cause. Tant que les deux composants ne normalisent pas le chemin de la même façon, chaque contrôle posé au niveau du proxy peut être franchi par une variante d’encodage ou de préfixe que le backend interprète différemment.
🦫 Explication de la marmotte 🍫
Section intitulée « 🦫 Explication de la marmotte 🍫 »Une adresse demandée à un site contient un chemin, par exemple /admin. Le serveur placé devant l’application reçoit cette adresse en premier. Il peut décider que certaines parties du site demandent une connexion et que d’autres restent publiques. L’application reçoit ensuite la demande et choisit la page ou la fonction à exécuter.
Le défaut apparaît quand les deux programmes ne lisent pas le chemin de la même manière. Certains caractères peuvent être écrits sous une forme codée. Une suite comme %2e représente un point. Deux points suivis d’une barre ont souvent le sens de remonter d’un dossier. Le serveur de devant peut voir un chemin qui commence par /public/ et laisser passer la demande. L’application, elle, peut transformer les caractères codés, retirer les segments qui font remonter, puis obtenir /admin.
La demande atteint alors une fonction protégée alors que la première vérification l’a classée comme publique. Si l’application ne vérifie pas elle-même le droit d’accès, elle peut livrer une information ou accepter une action réservée.
La correction consiste à refuser les écritures ambiguës avant le routage, puis à utiliser une seule version normalisée du chemin pour contrôler l’accès et choisir la fonction. Il faut aussi tester les formes codées, les barres répétées et les segments de remontée. Les journaux doivent garder la demande reçue et le chemin finalement utilisé, afin de repérer un désaccord.
À lire aussi
- Comment prévenir une attaque Host header injectionCette fiche démontre l'attaque par manipulation d'en-tête Host, ses impacts sur les liens générés et la validation d'origine, puis les contre-mesures.
- Attaque cache deception CDN et contre-mesuresDémonstration en laboratoire de l’attaque cache deception contre un CDN, ses conditions, ses impacts et les contre-mesures d’architecture.
- Comment contourner un rate limit par rotation d’en-têtesDémonstration d’une attaque de contournement de rate limit par rotation d’en-têtes ou d’identifiants client non vérifiés, avec contre-mesures d’architecture.
- Attaque MIME sniffing par fichier téléverséDémonstration d’une confusion de type MIME où un fichier téléversé est interprété comme du HTML ou du JavaScript exécutable faute de protection de type stricte.
Pages voisines
La citent
- Attaque cache deception CDN et contre-mesures
- Comment contourner un rate limit par rotation d’en-têtes
- NAT, translations et publication de services
Contourner un contrôle proxy par confusion de chemin
Elle cite
