
# CVE-2026-47627 के लिए प्रूफ-ऑफ-कॉन्सेप्ट एक्सप्लॉइट NVIDIA Triton Inference Server में पाथ ट्रैवर्सल भेद्यता, जो Zip-Slip के माध्यम से मनमानी फ़ाइल लेखन की ओर ले जाती है। विस्तृत विश्लेषण, प्रयोगशाला वातावरण और वन-क्लिक एक्सप्लॉइट स्क्रिप्ट शामिल हैं।
इस रिपॉजिटरी में पूर्ण शोध एवं PoC राइटअप (image
nvcr.io/nvidia/tritonserver:26.05-py3/server v2.69.0)। रिपॉजिटरी को हमेशाexplicitमोड में सेट किया गया है ताकि PoC बिना gRPC के माध्यम से restart किए चल सके।
| फ़ील्ड | मान |
|---|---|
| CVE | CVE-2026-47627 |
| CNA | [email protected] |
| प्रकाशन | 2026-08-18 (NVD Awaiting Analysis) |
| बुलेटिन | NV 5865 → https://github.com/NVIDIA/product-security/tree/main/2026/5865 |
| CVSS 3.1 | 9.8 CRITICAL 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/resource exhaustion तक बढ़ाया जा सकता है |
| क्रेडिट | Martin Brodeur |
ईमानदार टिप्पणी: CNA विवरण केवल
path traversal → DoSहै, बिना विवरण के। यह राइटअपr26.05..r26.06के diff और कंटेनर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, नया मॉडल लोड करने के लिए restart आवश्यक।POLL → हर सेकंड रिपॉजिटरी स्कैन, ऑटो-लोड, explicit लोड को ब्लॉक करता है।EXPLICIT (यह रिपॉजिटरी) → स्कैन नहीं करता, लेकिन gRPC load_model / POST /load खोलता है → gRPC के माध्यम से बिना restart PoC।पोर्ट:
| 8000 | HTTP REST | v2/health, v2/models, v2/repository |
| 8001 | gRPC | RepositoryModelLoad, ModelInfer |
| 8002 | मेट्रिक्स | Prometheus |
प्रारंभिक मॉडल:
model_repository/
├── echo_python/ (python backend, model.py)
│ └── 1/model.py
└── identity_onnx/ (onnxruntime, model.onnx ~1KB)
Quickstart:
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 जाँच (partial-match /models बनाम /models_evil) के हो, तो ../ escape हो सकता है।/dev/zero, /proc/self/mem हो, या tar में ../../ हो जो सिस्टम फ़ाइलों को overwrite करे → hang / OOM / crash।तीन शोध प्रश्न (प्रारंभिक रिपोर्ट से):
RepositoryModelLoad में model_name, EXECUTION_ENV_PATH tar entry, file: override, 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 — fix dcb315d + 72f3d9b:
// VULN (r26.05):
absolute_child.rfind(absolute_parent,0) != 0
// → bug partial-match: "/models" prefix dari "/models_evil/file" dianggap inside
// FIXED (r26.06):
canonical_child.find(canonical_parent,0)==0 &&
((child.size() > parent.size() && child[parent.size()]=='/') || child.size()==parent.size())
// → harus '/' setelah prefix, "/models_evil" sekarang correctly outside
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"
यही कारण है कि 26.05 में gRPC load_model('../tmp/pwn') → 400 INVALID_ARGUMENT (पहले से ब्लॉक)। लेकिन %2f (encoded /) लिटरल / न होने के कारण पास हो जाता है → 500 poll literal।
python_be.cc:292 EXECUTION_ENV_PATH — e520f8c7 test zipslip_test.py:
Tar बिना .. / absolute जाँच के extract किया जाता है। /models/poc_exploit/malicious_env.tar.gz से entry ../../poc_marker को /tmp/poc_marker (मॉडल डायरेक्टरी के बाहर) में extract किया जाता है। 26.06 में fix: ARCHIVE_EXTRACT_SECURE_NODOTDOT|NOABSOLUTEPATHS + IsChildPathEscapingParentPath जाँच।
रिपॉजिटरी server (r26.05..r26.06): केवल sagemaker_server.cc:1027 (RE2::FullMatch check) + http_server.cc:2440 (atoi→stoi), इस CVE के पथ से संबंधित नहीं।
निष्कर्ष: HTTP/gRPC के माध्यम से model_name पथ 26.05 में ValidateModelName द्वारा पहले से ब्लॉक है, लेकिन Zip-Slip tar अभी भी ब्लॉक नहीं है — यही इस रिपॉजिटरी के PoC द्वारा एक्सप्लॉइट किया गया है।
| वेक्टर | पेलोड | 26.05 परिणाम | प्रभावित? |
|---|---|---|---|
HTTP raw ../ | POST /v2/repository/models/../tmp/pwn/load | 404 (routing normalize) | ❌ |
HTTP encoded ..%2f | POST /v2/repository/models/..%2ftmp%2fpwn/load | 500 literal, poll /models/..%2ftmp%2fpwn मौजूद नहीं | ❌ (500 fake, ट्रैवर्सल नहीं) |
gRPC raw ../tmp/pwn | load_model('../tmp/pwn') | 400 INVALID_ARGUMENT | ❌ (पहले से ब्लॉक) |
gRPC encoded ..%2f | load_model('..%2ftmp%2fpwn') | 500 poll literal | ❌ |
file: override ../models_evil | file:../models_evil/pwn | 400/500 partial-match पर निर्भर | ⚠️ (IsChildPathEscapingParentPath bug पर निर्भर, लेकिन model_name के लिए ValidateModelName द्वारा पहले से ब्लॉक) |
EXECUTION_ENV_PATH tar ../../poc_marker | malicious_env.tar.gz entry ../../poc_marker | 200 + /tmp/poc_marker पर फ़ाइल | ✅ VULN |
पुराने राइटअप में
500 vs 400की उपमा गलत थी:500= फ़ाइल मौजूद नहीं (दरवाज़ा नहीं है),400= वैलिडेशन द्वारा अस्वीकृत (दरवाज़ा बंद है)। दोनों में प्रवेश नहीं होता। केवल200+/tmpमें फ़ाइल ही प्रमाण है।