# Traefik: Incomplete fix for CVE-2026-33433 + CVE-2026-39858 cross-cohort: headerField underscore-variant identity spoofing in BasicAuth / DigestAuth / ForwardAuth

Traefik's BasicAuth/DigestAuth/ForwardAuth stripped only canonical-cased spoofed identity headers, so underscore variants like X_Auth_User survived and spoofed identity at backends that normalize _ to -.

- Payout: Research
- Severity: high
- Program: Traefik
- Found by: matte1782
- Disclosed: 2026-08-06
- CVE: CVE-2026-54763
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-x677-9fxg-v5c5
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-x677-9fxg-v5c5
- Tags: area:web domain:web impact:integrity impact:privesc kind:research platform:ghsa sev:high tech:go technique:chained technique:incomplete-fix technique:source-review vuln:access-control vuln:auth-bypass

**Target:** Traefik HTTP auth middlewares (BasicAuth, DigestAuth, ForwardAuth) headerField/authResponseHeaders writeback
**Root cause:** The CVE-2026-33433 fix used req.Header.Del(headerField), and Go's CanonicalMIMEHeaderKey does not treat '_' as a separator, so underscore-variant headers survive stripping and are forwarded alongside Traefik's literal-key writeback.
**Steps:**
1. Deploy traefik:v3.6.14 with basicAuth users and `headerField: "X-Auth-User"` in dynamic config.
2. Control: `curl -u alice:secret123 -H "X-Auth-User: superadmin" http://localhost:8080/` — backend sees only `x-auth-user: alice`.
3. Bypass: `curl -u alice:secret123 -H "X_Auth_User: superadmin" http://localhost:8080/` — backend receives both `x-auth-user: alice` and `x_auth_user: superadmin`.
4. On backends normalizing `_`↔`-` (CGI/WSGI/PHP $_SERVER, nginx underscores_in_headers, Tomcat), the attacker value wins the identity lookup.
5. ForwardAuth variant: pre-inject `X_Auth_User` on the authResponseHeaders path with no credentials at all.
**How found:** Read the actual Go textproto.CanonicalMIMEHeaderKey semantics against the CVE-2026-33433 fix: noticed Del canonicalizes case and '-' but not '_', then checked the existing isManagedXHeader primitive from CVE-2026-39858 and saw it was never applied to the auth middlewares; confirmed with a Docker compose PoC on the fully patched release.
**Exploitation:** Attacker sends a request to a protected route with an underscore-variant identity header (e.g. `X_Auth_User: superadmin`); Traefik's Del only removes the dashed canonical form, and the fast proxy's DisableNormalizing() forwards the underscore header verbatim to the backend, which merges it with Traefik's writeback and trusts the attacker's value. On ForwardAuth authResponseHeaders the attacker needs no credentials.
**Escalation:** Cross-cohort chaining: the same underscore-variant primitive already accepted for X-Forwarded-* in CVE-2026-39858 was not extended to operator-configurable headerField/authResponseHeaders, raising impact from authenticated identity spoofing to pre-auth spoofing on ForwardAuth.
**Insight:** When a fix relies on a library's canonicalization (Go Header.Del/CanonicalMIMEHeaderKey), audit exactly which characters that function treats as separators — '_' is not one — and check whether a sibling advisory already established the equivalent threat model for a different header family that wasn't propagated.

## Proof of concept (copied exactly from the original report)

**curl with underscore-variant header bypassing BasicAuth**

```
$ curl -s -u alice:secret123 -H "X_Auth_User: superadmin" http://localhost:8080/
{
  ...
  "x-auth-user": "alice",
  "x_auth_user": "superadmin",
```

**Double-send canonical plus underscore header test**

```
$ curl -s -u alice:secret123 \
    -H "X-Auth-User: superadmin" \
    -H "X_Auth_User: superadmin" \
    http://localhost:8080/
{
  ...
  "x-auth-user": "alice",       # Traefik's writeback
  "x_auth_user": "superadmin",  # attacker's underscore — survived Del
```

**Vulnerable Del plus literal-key writeback code**

```
if b.headerField != "" {
    // TODO Deprecated we should add the header with canonical key.
    req.Header.Del(b.headerField)
    req.Header[b.headerField] = []string{user}
}
```

