GHSA-x677-9fxg-v5c5 Traefik: Incomplete fix for CVE-2026-33433 + CVE-2026-39858 cross-cohort: headerField underscore-variant identity spoofing in BasicAuth / DigestAuth / ForwardAuth
SummaryThere is a high severity vulnerability in Traefik's BasicAuth, DigestAuth, and ForwardAuth
middlewares. The fix for CVE-2026-33433 stripped canonical-cased spoofed identity headers
(e.g. `X-Auth-User`) before writing Traefik's own value, but did not account for
underscore-variant header names (e.g. `X_Auth_User`), which many backends normalize
identically to the dashed form. An attacker able to reach a protected route could inject
an underscore-variant header that survives Traefik's stripping and reaches the backend
alongside — or, on the unauthenticated ForwardAuth `authResponseHeaders` path, instead of
— the value Traefik intended to set, spoofing identity or authorization context. This is
fixed by setting the new `allowHeadersWithUnderscores: false` entry point option, which
strips all headers with underscores in their names before routing.Patcheshttps://github.com/traefik/traefik/releases/tag/v2.11.51https://github.com/traefik/traefik/releases/tag/v3.6.22https://github.com/traefik/traefik/releases/tag/v3.7.6For more informationIf you have any questions or comments about this advisory, please [open an issue](https://github.com/traefik/traefik/issues).Incomplete fix for CVE-2026-33433 + CVE-2026-39858 cross-cohort: `headerField` underscore-variant identity spoofing in BasicAuth / DigestAuth / ForwardAuthSummaryThe fix for CVE-2026-33433 (GHSA-qr99-7898-vr7c, "BasicAuth/DigestAuth Identity Spoofing via Non-Canonical headerField", patched in v2.11.42 / v3.6.12 / v3.7.0-ea.3) added `req.Header.Del(headerField)` before the literal-key writeback in `pkg/middlewares/auth/basic_auth.go` and `pkg/middlewares/auth/digest_auth.go`. Go's `Header.Del` calls `textproto.CanonicalMIMEHeaderKey` which canonicalizes ASCII CASE and treats `-` as a word separator — so the fix correctly strips canonical-cased attacker headers (`X-Auth-User`, `x-auth-user`, `X-AUTH-USER`, etc.).However, `textproto.CanonicalMIMEHeaderKey` does **NOT** treat `_` as a separator. Attacker-supplied **underscore-variant** headers such as `X_Auth_User` survive `Header.Del("X-Auth-User")` intact and are forwarded to the backend alongside Traefik's own writeback. Many common backends (CGI/WSGI per RFC 3875, PHP `$_SERVER`, nginx with `underscores_in_headers on`, Tomcat / Java EE servlet containers, ASGI/WSGI frameworks) normalize `_` ↔ `-` equivalently or expose both forms to application code that may read the attacker's value.This is the **direct cross-cohort sibling** of the threat model the maintainer accepted in **CVE-2026-39858** (GHSA-5m6w-wvh7-57vm, "Forwarded alias spoofing pre-auth decision bypass"), which fixed the underscore-variant of the X-Forwarded-* family via `isManagedXHeader` in `pkg/middlewares/forwardedheaders/forwarded_header.go`. The CVE-2026-39858 advisory body states verbatim:"When the backend normalizes underscore and dash header forms equivalently, an attacker can inject spoofed trust context — such as a trusted scheme or host — through the alias headers and bypass authentication on protected routes without valid credentials."The same threat model applies to the operator-configurable `headerField` (BasicAuth, DigestAuth) and `authResponseHeaders` (ForwardAuth, ingress-nginx snippet provider), but the underscore-handling primitive (`isManagedXHeader`) was not extended to those middlewares. I verified the bypass end-to-end on `traefik:v3.6.14` (the latest patched release containing both fixes) using a default-recommended canonical `headerField: "X-Auth-User"` config and reproduced the bypass with a single `curl -H "X_Auth_User: superadmin" ...` request alongside valid BasicAuth credentials.The defect is present in four code paths at HEAD `eec68dce064f843b4317c4393aaea81b6dea31d6`:`pkg/middlewares/auth/basic_auth.go:101-105` — BasicAuth `headerField``pkg/middlewares/auth/digest_auth.go:99-103` — DigestAuth `headerField``pkg/middlewares/auth/forward.go:304-310` — ForwardAuth `authResponseHeaders` per-name writeback`pkg/middlewares/ingressnginx/snippet/snippet.go:480-486` — Ingress-NGINX snippet `authResponseHeaders` per-name writebackThe ForwardAuth instance (#3) is particularly notable: the attacker does NOT need credentials. The `authResponseHeaders` mechanism is intended to copy identity headers from the trusted auth server only; the underscore-variant bypass lets an unauthenticated attacker pre-inject the same identity header before any auth happens.The fast proxy at `pkg/proxy/fast/proxy.go:139` explicitly calls `DisableNormalizing()` on the outgoing fasthttp request, guaranteeing that the underscore-variant header reaches the backend wire verbatim. The standard `httputil.ReverseProxy` path at `pkg/proxy/httputil/proxy.go:55` likewise copies `req.Header` keys as-is during the wire write.Affected versions`traefik` v3.6.x ≤ 3.6.14, v3.7.x ≤ 3.7.0-rc.2, v2.11.x ≤ 2.11.43, and all earlier versions sharing the same auth middleware architecture.The defect is present at HEAD post-CVE-2026-33433 fix (the fix added the `Del` line but the literal-key write defect-class survives for underscore variants).Root causeIn `pkg/middlewares/auth/basic_auth.go` at HEAD `eec68dc`:The TODO comment shows the maintainer is aware of the literal-key write problem in general (canonical-key write would solve the case-canonicalization issue more cleanly than the current `Del` + literal-write pair). The comment does not acknowledge the underscore-variant survival corollary.`pkg/middlewares/auth/digest_auth.go:99-103` and the two ForwardAuth paths follow the same `Del` + literal-write pattern. Each is independently exploitable; the underlying primitive defect is shared.The maintainer's gold-standard primitive for handling this exact threat class is `pkg/middlewares/forwardedheaders/forwarded_header.go:53-66`:This treats `_` ↔ `-` equivalence as a security requirement. It is reachable only via the static `XHeadersSet` membership check, which contains exclusively the X-Forwarded-* family + X-Real-Ip. Operator-configurable identity headers are out of scope of this primitive.Proof of conceptVerified on `traefik:v3.6.14` (the patched version, post-CVE-2026-33433 and post-CVE-2026-39858) using Docker compose. Full reproducer at https://github.com/<attacker-repo>/traefik-ht1a-poc; commands below are verbatim.Setup`docker compose up -d`.Test 1 (control — CVE-2026-33433 fix works for canonical case)The attacker's canonical `X-Auth-User: superadmin` was correctly stripped by Traefik's `Del`; the backend receives only Traefik's authenticated-user writeback `alice`.Test 2 (HT-1A bypass — underscore variant survives)The underscore-variant `x_auth_user: superadmin` reached the backend intact, despite the `Del("X-Auth-User")` having executed. The backend sees both forms.Test 3 (double-send — same result)The canonical attacker header is stripped (Test 1 behavior). The underscore variant is forwarded.Backend impactThe PoC's echo backend (`mendhak/http-https-echo`, Node.js) preserves both forms with the lowercase normalization Node.js applies. Application code reading `req.headers["x-auth-user"]` sees `alice`. Application code reading `req.headers["x_auth_user"]` sees `superadmin`.For backends that normalize `_` ↔ `-` equivalently — meaning the attacker's value wins:**CGI / WSGI / PHP `$_SERVER`** (RFC 3875 §4.1.18 — header name uppercased with `-` replaced by `_`): both `X-Auth-User` and `X_Auth_User` map to `HTTP_X_AUTH_USER`. The last-set wins per the WSGI server's iteration order; many servers (gunicorn, uwsgi without `--disable-logging`, waitress) preserve both. Note: Apache + mod_php with default `HttpProtocolOptions Strict` filters underscore-headers from `$_SERVER` (this PoC's PHP backend test demonstrated the filter); Apache + mod_python, Apache + mod_wsgi without the strict mode, nginx + uwsgi, nginx + gunicorn, nginx + FastCGI, and standalone WSGI servers do NOT filter.**nginx with `underscores_in_headers on`** (https://nginx.org/en/docs/http/ngx_http_core_module.html#underscores_in_headers): preserves underscore-variant headers and forwards them to upstream as separate values. Upstream application logic that does case-insensitive + underscore-insensitive matching (common pattern in security-sensitive code) merges them.**Tomcat / Java EE servlet containers**: `HttpServletRequest.getHeader(name)` is case-insensitive; underscore handling is container-specific. Many normalize.**Application middleware** (WAFs, log aggregators, security gateways, identity-aware proxies) that normalize header names before applying security policy: both forms collapse to the same authorization decision input.SeverityI propose **HIGH CVSS 7.5** for the BasicAuth / DigestAuth case and **CRITICAL CVSS 9.1** for the ForwardAuth `authResponseHeaders` case (the latter requires no credentials).**CVSS 3.1 vector (BasicAuth / DigestAuth)**: `AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N=7.5` — one step above CVE-2026-33433 (which the maintainer scored MEDIUM 5.1 because it required misconfigured non-canonical `headerField`). HT-1A works against the canonical / recommended `headerField` configuration, broader operational scope.**CVSS 3.1 vector (ForwardAuth `authResponseHeaders`)**: `AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N=9.1` — parallel to CVE-2026-39858 (HIGH 7.5) but achieves spoofing without credentials because the `authResponseHeaders` mechanism trusts headers exclusively from the auth server and the underscore variant defeats that trust boundary.CWEs:CWE-290 (Authentication Bypass by Spoofing)CWE-178 (Improper Handling of Case Sensitivity) — analogous to CVE-2026-29054CWE-345 (Insufficient Verification of Data Authenticity) — same as CVE-2026-35051Suggested fixTwo equivalent approaches:**1. Extend `Header.Del` to handle underscore variants** at the four call sites. Replace:with:This pairs the headerField primitive with the same `_` ↔ `-` equivalence that `isManagedXHeader` enforces for X-Forwarded-*.**2. Generalize the existing `isManagedXHeader` primitive** into a `stripHeaderAndVariants(headers http.Header, name string)` helper in the `forwardedheaders` package and call it from `basic_auth.go`, `digest_auth.go`, `forward.go`, and `snippet.go`. Reusing the existing gold-standard primitive is the cleanest fix and minimizes future drift.Either approach should also resolve the `// TODO Deprecated we should add the header with canonical key.` debt at `basic_auth.go:102` and `digest_auth.go:100` by writing to the canonical key (`Header.Set(canonical, user)`) instead of the literal `b.headerField`.Why this is a Pattern-8 sibling, not a new CVE classThe combination of:CVE-2026-33433's fix scope (case-canonicalization for `headerField`)CVE-2026-39858's fix scope (underscore-variant for `XHeadersSet`)The defective primitive remaining at HEAD (the `Del` + literal-write pair at four call sites)establishes that the maintainer accepts the threat model and has architectural primitives to fix it — but did not cross the two cohorts. The "primitive depth-audit" of the CVE-2026-33433 fix (reading the actual `Header.Del` implementation against the documented threat model and Go's canonicalization semantics) reveals the gap.I confirmed there is no public PoC mentioning underscore-variant siblings of CVE-2026-33433 (WebSearched 2026-05-23). The fix-flurry from the April 2026 security release batch addressed the X-Forwarded family but not the headerField family.CreditMatteo Panzeri (GitHub `matte1782`). CVE credit requested.AI-assistance disclosureStatic analysis, hypothesis writing, and hostile-review confirmation were assisted by Anthropic Claude (Opus 4.7). Live PoC reproduction, code-citation verification, and submission decision were made by the human author.
CVSS v4.0 score: 7.8