NginxUI权限绕过与备份解密漏洞?可能RCE哦|CVE-2026-27944复现&研究

admin 2026-03-10 02:50:28 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 该文档深入分析了NginxUIv2.3.3前版本的CVE-2026-27944漏洞。核心缺陷在于/api/backup接口被错误置于匿名路由组导致未授权访问,且响应头泄露AES密钥。攻击者可利用此链下载并解密备份文件窃取敏感数据。文中详细展示了环境搭建、漏洞复现、流量特征及源码审计,揭示了路由配置错误与密钥明文传输的严重危害,建议升级或严格限制接口访问权限。 综合评分: 98 文章分类: 漏洞分析,漏洞POC,代码审计,渗透测试,实战经验


cover_image

Nginx UI 权限绕过与备份解密漏洞?可能RCE哦 | CVE-2026-27944 复现&研究

原创

404号浪漫 404号浪漫

404号浪漫

2026年3月9日 23:37 北京

0x0 背景介绍

Nginx UI是一个用于管理Nginx服务器的开源网页界面工具,在2.3.3 之前的版本中,/api/backup接口存在权限控制缺陷,允许未经身份验证的访问。同时,该接口在响应头的 X-Backup-Security字段中错误地公开了用于加密备份文件的密钥。攻击者可以远程下载完整的系统备份文件,并利用响应头中的密钥直接解密,从而获取服务器的敏感数据,包括用户凭据、会话令牌、SSL 私钥以及Nginx配置文件

漏洞详情

| 漏洞类型 | 影响版本 | 利用复杂度 | CVE编号 | | — | — | — | — | | 信息泄露 | ≤v2.3.2 | 低 | CVE-2026-27944 |

攻击效果:

  • 信息泄露,获取敏感信息,甚至控制服务器。

0x1 环境搭建(Ubuntu24)

1.1-Ubuntu24+Docker搭建配置

  • 另存为install.sh运行
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;"=============================================="
  • 后续访问web服务进行搭建


0x2 漏洞复现

2.1-手动复现

  • 漏洞复现

  • python参考:
https://github.com/Kai-One001/cve-/blob/main/CVE-2026-27944-Nginx-UI.py
  • 信息泄露+解密

  • 意想不到的结果,通过获取的X-Backup-Security进行恶意上传,详情见下文分析

 EXP没公开哈,按需索取或者自行研究,你与第一只差一步之遥

  • 容器验证

2.2-复现流量特征 (PCAP)

  • 未授权请求backup接口

  • 其他利用手段,上传恶意文件


0x3 漏洞原理分析

3.1-架构与模块定位

Nginx UI 的后端使用 Gin 路由分组实现分层访问控制:最外层统一挂在 /api,其中一部分路由(例如登录、公用接口、自检)允许匿名访问;另一部分路由通过 middleware.AuthRequired() 强制认证

这次漏洞恰好落在 “匿名访问区” 的边界上:备份模块 api/backup 被初始化在 /api 根组上,而该根组只做了 IP 白名单校验(且默认空白名单等价于全放行),**没有任何身份认证**

3.2 漏洞链条涉及的核心文件

| 层级 | 文件 | 角色定位 | 在漏洞链条中的职责 | | — | — | — | — | | 入口层(路由) | router/routers.go | 全局路由与中间件编排 | 决定 backup.InitRouter(root) 落在哪个路由组(是否受 AuthRequired 保护) | | 入口层(模块路由) | api/backup/router.go | 备份模块路由声明 | 注册 GET /backup 与 POST /restore(均未显式加鉴权) | | 控制器层(危险输出) | api/backup/backup.go | 备份下载接口 | 生成备份并把 AESKey:IV 写进 X-Backup-Security 响应头 | | 逻辑层(备份构建) | internal/backup/backup.go | 备份打包与加密 | 收集配置与数据库文件、压缩、AES-CBC 加密,并把 key/iv 作为结果返回上层 | | 逻辑层(敏感文件选择) | internal/backup/backup_nginx_ui.go | nginx-ui 文件收集 | 明确把 app.ini 与 *.db(用户 / 令牌等)纳入备份 | | 安全边界(中间件) | internal/middleware/middleware.gointernal/middleware/ip_whitelist.go | 认证与 IP 白名单 | AuthRequired() 负责真正的认证;IPWhiteList() 默认空白名单全放行,构成 “看似有门禁,实则无门禁” 的错觉 |

3.3 锁定关键路径:从 /api/backup 开始

3.3.1 第一步:路由层确认 “它到底挂在哪个访问域”

在 router/routers.go 里,/api 根组只绑定了 middleware.IPWhiteList(),然后直接初始化了一批 “无需认证” 的路由模块,其中就包含 backup.InitRouter(root):

//61:103:d:\环境-下载中心[1]nginx-ui-2.3.2\nginx-ui-2.3.2\router\routers.go    root := r.Group("/api", middleware.IPWhiteList())   {       public.InitRouter(root)     crypto.InitPublicRouter(root)       user.InitAuthRouter(root)       license.InitRouter(root)        system.InitPublicRouter(root)       system.InitSelfCheckRouter(root)        backup.InitRouter(root)     // Local-only routes (no proxy) - authorization required        local := root.Group("/", middleware.AuthRequired())     {           llm.InitLocalRouter(local)      }       // Authorization required and not websocket requestg&nbsp;:= root.Group("/", middleware.AuthRequired(), middleware.Proxy())     {           // ... lots of private routers ...          backup.InitAutoBackupRouter(g)      }
  • 这段代码就是 “危险开关面板”:同样叫 backup 的模块,InitAutoBackupRouter(g) 被放进了需要认证的私有组,
  • lnitRouter(root) 却被放进了匿名组。这意味着 GET /api/backup 和 POST /api/restore 都天然不受 AuthRequired() 保护

3.3.2 第二步:模块路由确认 “具体暴露了什么端点”

在 api/backup/router.go:

//8:21:\nginx-ui-2.3.2\api\backup\router.gofunc&nbsp;InitRouter(r&nbsp;*gin.RouterGroup) {    r.GET("/backup",&nbsp;CreateBackup) r.POST("/restore", middleware.EncryptedForm(),&nbsp;RestoreBackup)}
  • 到这里已经把漏洞入口锁定为(这两个接口都没有在这里添加任何鉴权中间件):
- GET /api/backup(下载备份)- POST /api/restore(上传恢复)

3.3.3 第三步:追到 “最后一道失守的防线”

接着看 CreateBackup 的实现,需特别留意两点:

  • 是否会把备份内容直接回传给客户端(大文件下载通常意味着高价值数据流出)
  • 是否会在响应头 / 响应体泄露解密要素(key/iv/token)
//15:42:\nginx-ui-2.3.2\api\backup\backup.gofunc&nbsp;CreateBackup(c *gin.Context)&nbsp;{    result, err := backup.Backup()if&nbsp;err !=&nbsp;nil&nbsp;{        cosy.ErrHandler(c, err)return   }// Concatenate Key and IV  securityToken := result.AESKey +&nbsp;":"&nbsp;+ result.AESIv// Prepare response content    reader := bytes.NewReader(result.BackupContent) modTime := time.Now()// Set HTTP headers for file download  fileName := result.BackupName   c.Header("Content-Description",&nbsp;"File Transfer")   c.Header("Content-Type",&nbsp;"application/zip")    c.Header("Content-Disposition",&nbsp;"attachment; filename="+fileName)  c.Header("Content-Transfer-Encoding",&nbsp;"binary")    c.Header("X-Backup-Security", securityToken)&nbsp;// Pass security token in header  c.Header("Expires",&nbsp;"0")   c.Header("Cache-Control",&nbsp;"must-revalidate")   c.Header("Pragma",&nbsp;"public")// Send file content   http.ServeContent(c.Writer, c.Request, fileName, modTime, reader)}

最后一道失守的防线就是这里的:

  • c.Header(“X-Backup-Security”, securityToken),它把本应只存在于服务器内存 / 安全存储的 AESKey 与 AESIv(base64)拼接后直接给了客户端
  • 结合上一节的 “匿名可访问”,这就形成了 CVE 描述的核心链:未认证下载 + 同包返回解密密钥

3.4 剖析边界缺失:预期安全设计 vs 实际实现

3.4.1 预期的安全设计(合理推断)

从 router/routers.go 的分组风格可以看出项目的安全意图:

  • api 根组:允许匿名访问一些 “公共 / 初始化 / 自检” 接口
  • api 私有组(middleware.AuthRequired()):业务管理面板、配置管理等

备份接口在 “业务语义” 上属于高危操作(读取配置、读取数据库、打包私钥 / 证书、导出会话令牌等),按常识应该属于私有组。

此外,备份逻辑里确实做了 AES 加密,看起来像是 “就算备份泄露,也不会被读懂”。但这个设计要成立,有一个前提:解密密钥不能随备份一并发给同一个匿名请求方

3.4.2 实际实现:边界为什么等价于 “无”

边界缺失点 A:路由组放错位置

backup.InitRouter(root) 被放到了匿名组 root := r.Group("/api", middleware.IPWhiteList())这意味着:
  • 只要 IP 白名单不生效(默认空、或被部署者忽略),该端点对公网直接暴露。

边界缺失点 B:IP 白名单的默认行为是 “全放行”

IPWhiteList() 的关键逻辑:

//11:25:\nginx-ui-2.3.2\internal\middleware\ip_whitelist.gofunc&nbsp;IPWhiteList()&nbsp;gin.HandlerFunc {return&nbsp;func(c *gin.Context)&nbsp;{        clientIP := 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;{         c.Next()return      }if&nbsp;!lo.Contains(settings.AuthSettings.IPWhiteList, clientIP) {            c.AbortWithStatus(http.StatusForbidden)return       }       c.Next()    }}
  • 当 settings.AuthSettings.IPWhiteList 为空时(这在 “默认安装、未配置额外安全项” 的场景里非常常见),中间件会直接 c.Next(),等价于不做任何限制

补充证据:默认配置的 “零值陷阱”

追踪至 internal/kernel/boot.go 中的初始化逻辑,系统在检测到配置文件缺失时会自动生成默认配置并持久化:

// internal/kernel/boot.gofunc&nbsp;InitNodeSecret()&nbsp;{&nbsp; &nbsp;&nbsp;if&nbsp;settings.NodeSettings.Secret ==&nbsp;""&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// ... 生成随机 Secret ...&nbsp; &nbsp; &nbsp; &nbsp; err := settings.Save()&nbsp;// 关键点:将包含零值配置的 struct 写入磁盘&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;err !=&nbsp;nil&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; logger.Error("Error save settings", err)&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }}
  • 在Go语言机制中,结构体AuthSettings中的 IPWhiteList 字段(类型为 [] string)在未显式赋值时,其零值(Zero Value)为空切片 []
  • 这意味着:任何全新部署的 Nginx UI 实例(尤其是 Docker 容器化环境),在首次启动自动生成 app.ini 时,其 IP白名单必然为空
  • 结合 middleware/ip_whitelist.go 中 “空名单即放行” 的逻辑,这导致所有新安装的环境在默认状态下即对公网完全暴露高危接口,无需管理员进行任何错误的配置操作

边界缺失点 C:加密并没有提供防泄露能力,因为 key/iv 同步泄露

在 internal/backup/backup.go 中,备份会生成随机 key、iv,并以 base64形式返回给上层:

//58:184:\nginx-ui-2.3.2\internal\backup\backup.go    key, err := GenerateAESKey()// ...  iv, err := GenerateIV()// ...// Encode encryption keys as base64 for safe transmission/storage  keyBase64 := base64.StdEncoding.EncodeToString(key) ivBase64 := base64.StdEncoding.EncodeToString(iv)// Assemble final backup result    result := Result{       BackupContent: buffer.Bytes(),      BackupName: &nbsp; &nbsp;backupName,        AESKey: &nbsp; &nbsp; &nbsp; &nbsp;keyBase64,       AESIv: &nbsp; &nbsp; &nbsp; &nbsp; ivBase64,    }// encryptFile 内部逻辑推断 (基于 Go 标准实践):// block, _ := aes.NewCipher(key) // 32 bytes key -> AES-256// mode := cipher.NewCBCEncrypter(block, iv)// paddedData := pkcs7Pad(data, block.BlockSize())// mode.CryptBlocks(ciphertext, paddedData)
  • 在逻辑层看来,把 key/iv 放进 Result 也许是为了展示,让用户下载后能恢复。但控制器层的做法是把它们塞进响应头,导致任何能触发下载的人都能同步获得解密材料
  • 在请求方面,只需使用任何支持 AES-256-CBC的标准工具(如 Python pycryptodome、OpenSSL 或 Go 原生库),配合响应头中的 X-Backup-Security (格式为 Base64(Key):Base64(IV)), 即可在毫秒级时间内完成解密

加密在此处仅起到了 “混淆” 作用,而未提供任何实质性的机密性保护

3.4.3 一个 “误导性的安全感” 细节:前端的使用方式

前端 app/src/api/backup.ts 明确写了,为了拿响应头,要使用 returnFullResponse,这间接佐证了设计意图:X-Backup-Security 的确被当作正常功能的一部分:

//46:76:\nginx-ui-2.3.2\app\src\api\backup.ts&nbsp;&nbsp;createBackup() {&nbsp; &nbsp;&nbsp;return&nbsp;http.get('/backup', {&nbsp; &nbsp; &nbsp;&nbsp;responseType:&nbsp;'blob',&nbsp; &nbsp; &nbsp;&nbsp;returnFullResponse:&nbsp;true,&nbsp; &nbsp; })&nbsp; },&nbsp;&nbsp;restoreBackup(options: RestoreOptions) {&nbsp; &nbsp;&nbsp;// ...&nbsp; &nbsp;&nbsp;return&nbsp;http.post('/restore', formData, {&nbsp; &nbsp; &nbsp;&nbsp;headers: {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;'Content-Type':&nbsp;'multipart/form-data;charset=UTF-8',&nbsp; &nbsp; &nbsp; },&nbsp; &nbsp; &nbsp;&nbsp;crypto:&nbsp;true,&nbsp; &nbsp; })&nbsp; },
  • 这意味着漏洞不是 “偶然泄露”,而是 “按功能设计输出”。当这个功能被放到匿名可访问的AP组里时,风险才被放大

3.5 推导最大危害

那么就不能局限在泄露,试着扩大下战果

3.5.1 备份内容包含什么敏感物

internal/backup/backup_nginx_ui.go 明确收集两类:
  • nginx-ui 配置文件:cosysettings.ConfPath → 固定写入备份目录为 app.ini
  • nginx-ui 数据库文件:.db(SQLite 文件常见命名)
16:46:\nginx-ui-2.3.2\internal\backup\backup_nginx_ui.go// Always save the config file as app.ini, regardless of its original name    destConfigPath := filepath.Join(destDir,&nbsp;"app.ini")// ..   dbName := settings.DatabaseSettings.GetName()   dbFile := dbName +&nbsp;".db"// Database directory is the same as config file directory dbDir := filepath.Dir(configPath)   dbPath := filepath.Join(dbDir, dbFile)// Copy database file
  • 此外,backupNginxFiles 会把 nginx 配置目录整体复制进备份:
//51:65:\nginx-ui-2.3.2\internal\backup\backup_nginx_ui.gofunc&nbsp;backupNginxFiles(destDir&nbsp;string)&nbsp;error&nbsp;{    nginxConfigDir := nginx.GetConfPath()// Copy nginx config directoryif&nbsp;err := copyDirectory(nginxConfigDir, destDir); err !=&nbsp;nil&nbsp;{return&nbsp;cosy.WrapErrorWithParams(ErrCopyNginxConfigDir, err.Error())    }return&nbsp;nil}

综合来看,攻击者解密备份后,常见可得信息包括(取决于你的部署内容):

  • 用户凭据 / 哈希、会话令牌、2FA/Passkey 相关数据(通常存于 *.db)
  • Nginx站点配置(可能包含 upstream内网地址、鉴权口令、反代目标、访问控制策略)
  • TLS 证书与私钥(如果你的 Nginx conf 目录里存放 / 引用了可读取的key 文件或把 key 放进了同目录)
  • app.ini 中的JWT secret、Node secret、数据库连接信息、第三方通知凭据等(具体字段取决于配置)

3.5.2 典型攻击链(未认证 → 全量备份 → 即时解密)

把上面的实现拼起来,攻击链几乎是 “一步到位”:

1.&nbsp;**匿名请求**&nbsp;GET /api/backup2.&nbsp;服务端返回 application/zip 的备份文件流3.&nbsp;同时在响应头给出 X-Backup-Security:&nbsp;<base64key>:<base64iv>4.&nbsp;攻击者本地 AES-CBC 解密内部 hash_info.txt、nginx-ui.zip、nginx.zip,再解压拿到配置与数据库
  • 这就是 “加密存在但不构成安全性” 的典型反例:密钥与密文同路返回时,加密只剩下格式上的复杂度,而没有任何保密价值

3.5.3 进一步的 “极限危害” 推演:结合未鉴权恢复接口

追踪至 api/backup/restore.go中的 RestoreBackup函数,我们发现该接口不仅缺乏身份认证,还直接暴露了服务重启的控制权:

/**虽然CVE 描述重点是下载与解密,但在该版本里 POST /api/restore 同样位于匿名组(见上文),这就有一个更具破坏性的链条可能性**/

// api/backup/restore.gofunc&nbsp;RestoreBackup(c *gin.Context)&nbsp;{&nbsp; &nbsp;&nbsp;// 1. 直接读取表单参数,无任何鉴权&nbsp; &nbsp; restoreNginx := c.PostForm("restore_nginx") ==&nbsp;"true"&nbsp; &nbsp;&nbsp;// ...&nbsp; &nbsp; securityToken := c.PostForm("security_token")&nbsp;&nbsp; &nbsp;&nbsp;// 2. 执行解密与文件覆盖 (内部逻辑包含直接写入系统配置目录)&nbsp; &nbsp; result, err := backup.Restore(options)&nbsp; &nbsp;&nbsp;// 3. 【高危】若参数为 true,异步触发 Nginx 重启,使恶意配置立即生效&nbsp; &nbsp;&nbsp;if&nbsp;restoreNginx {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;go&nbsp;func()&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; time.Sleep(2&nbsp;* time.Second)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; nginx.Restart()&nbsp;// 攻击者控制的配置在此刻被加载&nbsp; &nbsp; &nbsp; &nbsp; }()&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;// ...}
  • 攻击者先下载并解密备份,获得完整 nginx配置与 nginx-ui配置 / 数据库;
  • 然后构造恢复请求,触发 restoreNginxConfigs() 对 nginx conf 目录执行清理与覆盖,并在 api/backup/restore.go 里异步触发 nginx.Restart();
  • 若攻击者能将恶意 nginx 配置写入(例如开启危险模块、反代内网管理端、篡改站点路由),可能造成:
PS:虽然不能写入 /etc/passwd 等系统文件,但覆盖 Nginx 配置文件本身足以导致 RCE(通过 proxy_pass 劫持流量或 lua_exec 执行命令)
  • 内网横向与SSRF入口的建立(通过Nginx反代策略)
  • 访问控制绕过(重写allow/deny、auth_basic等)
  • 服务中断(配置清空 / 写入无效导致 Nginx 无法启动)

构造恢复请求

- 恢复接口为 POST /api/restore,需要使用 multipart/form-data 格式提交以下字段(接口只校验非空与 : 分割、base64&nbsp;解码可行):- restore_nginx:设置为&nbsp;true(触发 Nginx 重启)- restore_nginx_ui:可选,true&nbsp;或&nbsp;false(触发 Nginx UI 重启,可能造成 UI 中断)- verify_hash:设置为&nbsp;false(跳过哈希校验,避免因文件被篡改而失败)- security_token:<base64Key>:<base64Iv>,即从 X-Backup-Security 获取的值- backup_file:上传的文件,即之前下载的 backup.zip/或其他

请在微信客户端打开

从api/backup/restore.go可以看到若 restore_nginx=true**,将异步触发 nginx.Restart()**;若 restore_nginx_ui=true**,将触发 risefront.Restart()**(见 RestoreBackup 末尾的 goroutine**)

这里需要强调:internal/backup/restore.go 对 zip-slip(目录穿越)做了相对严格的防护(.. 检测 + Abs 前缀校验 + symlink 限制),所以不能将它直接定性为 “任意文件写入”。但 未授权触发 “清理并覆盖nginx conf + 重启” 本身就足以构成高危(受限目录下的配置文件覆盖 )


0x4 修复建议

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

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

2、临时防护措施:

  • 限制访问:临时限制对 /api/backup模块外部访问

  • 防火墙拦截:配置规则,拦截对模块异常的请求(/api/backup, /api/restore ),关注敏感请求

  • 强化认证:只有经过严格认证的用户才能访问,启用HTTP Basic Auth (基本认证)


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

/**无奖竞猜,大家有搭建小龙虾嘛~有没有真实用起来的**/


免责声明:

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

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

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

本文转载自:404号浪漫 404号浪漫 404号浪漫《Nginx UI 权限绕过与备份解密漏洞?可能RCE哦 | CVE-2026-27944 复现&研究》

从头顺到尾的渗透测试 网络安全文章

从头顺到尾的渗透测试

文章总结: 本文记录了一次上线前授权渗透测试全过程。目标为登录系统,通过字典爆破获取大量弱口令账号,登录参数存在SQL注入,目录扫描发现敏感路径,系统内还存在X
评论:0   参与:  0