资讯 Nginx UI 六个 CVE 同一根因:AuthRequired 只验 JWT,不验会话从哪来

2026-11-01 21:04:24

Nginx UI 六个 CVE 同一根因:AuthRequired 只验 JWT,不验会话从哪来

Nginx UI v2.5.0(2026-10-09)修掉六个高危 CVE,全部影响 2.0.0–2.4.x。这六个洞表面上看分散在登录、CSRF、备份、集群、升级几个模块,根因是一条:AuthRequired 只检查 JWT 与 OTP,不调用 RequireSecureSession——而只有后者才验证「这个会话是在有第二因子的前提下建立的」。

CVE缺陷类型
CVE-2026-107808只配 passkey 的账号,仅凭密码就发会话认证绕过
CVE-2026-107809带 JWT 的 cookie 被 AuthRequired 接受,不校验 CSRF tokenCSRF
CVE-2026-107810备份恢复时解压出的符号链接指向线上配置配置注入
CVE-2026-107811/api/nodes 发的 node token 被 AuthRequired 接受跨节点认证绕过
CVE-2026-107813cluster 相关端点走 AuthRequired 但没有 RequireSecureSession陈旧 JWT 提权
CVE-2026-107812升级包 digest 只对同源镜像校验镜像被篡改 → RCE

根因

AuthRequired 是管理端点的通用门禁,但它只做「JWT 有效 + OTP 通过」的判断。会话是从哪条路径签发的、签发时有没有第二因子,它不问。于是四类问题同时成立:

  • 加入 passkey 之前偷到的 JWT 仍长期有效(CVE-2026-107813);
  • passkey-only 账号仅凭密码就能拿到会话(CVE-2026-107808);
  • JWT cookie 跨源可用且无 CSRF 保护(CVE-2026-107809);
  • /api/nodes 的 node token 通过 AuthRequired 被映射成 initUser(CVE-2026-107811)。

CVE-2026-107808:passkey-only 账号少了第二因子

nginx-ui 支持 TOTP 与 WebAuthn passkey 两种第二因子,任一配置即报告 2FA 已启用。但 POST /api/login 只在存在 TOTP secret 时强制第二因子。

model/user.go:

func (u *User) EnabledOTP() bool     { return len(u.OTPSecret) != 0 }
func (u *User) EnabledPasskey() bool { /* true if a passkey row exists */ }
func (u *User) Enabled2FA() bool     { return u.EnabledOTP() || u.EnabledPasskey() }

api/user/auth.go 的 Login 里只写了 if u.EnabledOTP() { ... 要求 OTP ... },passkey-only 账号直接落到 GenerateJWT。RequireSecureSession() 同样只用 EnabledOTP() 判断,所以 passkey-only 用户在敏感操作上也被免掉 step-up。

PoC 三行:

GET /api/2fa_status
→ {"enabled":true,"otp_status":false,"passkey_status":true}

POST /api/login   # 只提交密码
→ HTTP 200 {"message":"ok","code":200,"token":"eyJ..."}

# 有 TOTP 的账号同样请求返回 code 199(需要第二因子)

影响面比「登录绕过」大:nginx-ui 里所有已认证用户实际都是管理员,terminal 功能能给宿主机 shell,账号接管等于控制受管 nginx 与主机。

其余几条的攻击面

CVE-2026-107809(CSRF):带 JWT 的 cookie 被 AuthRequired 直接接受,不校验 CSRF token,且 cookie 跨源可用。攻击者页面诱导已登录的管理员浏览器发请求,即可借其会话执行管理操作。

CVE-2026-107810(配置注入):备份恢复时,解压出的符号链接可以指向线上配置目录。恢复一个构造过的备份,写入目标就落到解压目录之外,等于改配置。

CVE-2026-107811(跨节点认证绕过):/api/nodes 签发的 node token 被 AuthRequired 接受并映射成 initUser,拿到 node token 的一方可以直接以该身份调用管理端点。

CVE-2026-107813(陈旧 JWT 提权):cluster 相关端点走 AuthRequired 但没有 RequireSecureSession,在启用第二因子之前签发的旧 JWT 依然能进入这些端点。

CVE-2026-107812(镜像篡改 → RCE):升级包 digest 只对同源镜像校验,非同源镜像提供的包不受 digest 约束,篡改后的包会被装上去。

同一条线上更早的两个洞

CVE-2026-84314(CVSS 8.1):POST /api/users/:id 在修改他人密码时没有 RequireSecureSession,而自助改密路径有。任何已认证的低权限账号可改掉初始管理员的密码,使既有管理员 token 失效,再用新密码登录。修复在 2.4.1(commit 23d1934,把 RequireSecureSession 加到用户管理、备份、证书、DNS 凭据路由)。

CVE-2026-33032:影响 2.3.5 及以下。MCP 集成暴露 /mcp 与 /mcp_message,/mcp 有 IP 白名单 + AuthRequired,/mcp_message 只有 IP 白名单——而默认白名单为空被当成「允许全部」。任何网络攻击者都能调用全部 MCP 工具(重启 nginx、增删改配置、触发 reload),CVSS 9.8。

2.5.0 的修复

  • AuthRequired 在管理端点上调用 RequireSecureSession;
  • 备份恢复校验符号链接目标在解压目录内;
  • 升级 digest 用签名 manifest,而非仅同源;
  • node token 限定到来源节点。

升级与加固清单

  1. 升级到 2.5.0+;只跑过 2.4.x 且未装 2.4.1 的,先到 2.4.1 再往上。
docker inspect --format '{{index .Config.Labels "org.opencontainers.image.version"}}'
  1. 轮换密码与 API token,假设旧 token 已泄露:管理员密码、自助改密后的密码、/api/nodes 相关 node token 全部重发。
  2. 使所有活跃会话失效——旧 JWT 在修复前的窗口内可能已被取走,RequireSecureSession 不会追溯作废它们。
  3. 审计 users 表与管理员操作:看有没有非预期的密码变更、新增节点、备份恢复记录。
  4. 把管理界面限制在可信网络或 VPN 后,不要把 8080/443 直接暴露在公网。
  5. 在反向代理上对 /api/users/ 限流,并对 POST 告警:
limit_req_zone $binary_remote_addr zone=nginxui_users:10m rate=5r/m;

location /api/users/ {
limit_req zone=nginxui_users burst=3 nodelay;
limit_req_status 429;
proxy_pass http://127.0.0.1:9000;
}

# 记录 POST 以便告警
log_format nginxui_post '$remote_addr "$request" $status';
access_log /var/log/nginx/nginxui_post.log nginxui_post;

如果暂时无法升级,最短路径是把管理端口的访问控制收到反代层,并对 /api/users/、/api/nodes、cluster 端点做同样的来源限制——它们都是这六个洞的落点。

复制全文 生成海报 Nginx 安全 运维 认证

推荐文章

程序员茄子在线接单