2026年7月2日 · 深度技术研究


导语

2026年5月13日,安全公司 depthfirst 公开披露了 CVE-2026-42945(代号 NGINX Rift)——一个存在于 NGINX 核心脚本引擎中的堆缓冲区溢出漏洞。该漏洞自 2008 年随 NGINX 0.6.27 引入,潜伏长达 18 年,影响了从 0.6.27 到 1.30.0 的所有开源版本。更令人震惊的是,这个漏洞不是由人类安全研究员发现的,而是由 depthfirst 的自主 AI 分析系统在仅 6 小时的源代码扫描中发现的。

全球约三分之一的 Web 服务器运行 NGINX,这个漏洞的存在意味着大量基础设施可能长期处于风险之中。


一、漏洞技术原理:两阶段引擎的"信任裂痕"

1.1 NGINX 脚本引擎的两阶段模型

NGINX 的 rewrite 模块采用两阶段处理模型来处理包含正则捕获组的 URI 重写:

  1. 长度计算阶段:遍历所有指令,计算最终字符串所需缓冲区大小,然后一次性分配内存。
  2. 数据复制阶段:将实际数据写入已分配的缓冲区。

这个设计本身是合理的——预先计算大小可以避免动态扩容带来的性能开销。问题在于,两个阶段对同一个标志位的解读不一致。

1.2 根本原因:is_args 标志的状态漂移

漏洞的核心是一个名为 is_args 的标志位,它指示当前 URI 是否包含查询参数(即是否存在 ?)。

触发条件需要三条规则同时存在:

# 规则1:rewrite 的替换字符串包含 ?
rewrite ^/api/(.*)$ /internal?migrated=true;

# 规则2:后续指令使用未命名正则捕获组($1, $2 等)
set $original_endpoint $1;

# 规则3:再一条 rewrite / if / set 指令
rewrite ^/proxy/(.*)$ http://backend/$original_endpoint break;

漏洞触发链:

  1. 规则1 中 ngx_http_script_start_args_code 函数在主引擎上设置 e->is_args = 1
  2. 规则2 的长度计算阶段创建了一个全新的子引擎 lele.is_args = 0(全新清零)
  3. 长度计算函数 ngx_http_script_copy_capture_len_code 基于 le.is_args = 0,返回原始未转义的捕获长度
  4. 但数据复制阶段运行在主引擎上,e->is_args 仍为 1,触发 ngx_escape_uri(..., NGX_ESCAPE_ARGS) URI 转义
  5. +%& 等字符从 1 字节扩展为 3 字节(如 +%2B
  6. 实际写入量 raw_size + 2×N 远超分配的缓冲区大小 raw_size堆缓冲区溢出

1.3 为什么人类审计看不到

单独审查每个函数,逻辑都是"正确的":

  • start_args_code 设置标志是合理的——URI 确实有查询参数
  • copy_capture_len_code 不转义也是合理的——子引擎认为没有查询参数
  • copy_capture_code 执行转义也是合理的——主引擎知道有查询参数

问题只出现在跨阶段、跨指令的交互中。人类代码审计很难捕捉这种"每个函数都对,但组合起来错"的微妙状态不一致。这是典型的并发/状态机 bug 类别——只是发生在单线程的两阶段处理中,更加隐蔽。


二、从溢出到 RCE:跨请求堆风水的利用链

depthfirst 公开的 PoC 展示了从堆溢出到远程代码执行的完整利用链:

2.1 利用前提

NGINX worker 进程由 master fork 产生,堆布局在每次崩溃后依然确定(因为 fork 创建相同内存布局的子进程)。这为"跨请求堆风水"提供了可预测的基础。

2.2 利用步骤

  1. 控制堆布局:发送多个精心构造的请求,使受害者连接的内存池(ngx_pool_t)恰好邻接在攻击者请求池旁边
  2. 触发溢出:发送包含大量可转义字符(如连续 +)的 URI,使转义后数据溢出堆边界
  3. 覆盖 cleanup 指针:溢出数据精确覆盖相邻池偏移 64 处的 cleanup 指针
  4. 喷洒伪造结构:通过 POST body 向堆中注入伪造的 ngx_pool_cleanup_s 结构,使 handler 指向 system()data 指向命令字符串
  5. 触发清理:关闭受害者连接,ngx_destroy_pool 遍历 cleanup 链表时执行任意命令

2.3 限制

当前公开的 RCE PoC 针对 ASLR 关闭的系统。在 ASLR 开启的系统上,直接可靠的 RCE 仍具挑战性,但 DoS(worker 进程崩溃重启)在所有受影响配置下均可稳定触发


三、影响范围:比你想象的大

产品 受影响版本
NGINX Open Source 0.6.27 – 1.30.0
NGINX Plus R32 – R36
NGINX Ingress Controller 3.5.0 – 3.7.2; 4.0.0 – 4.0.1; 5.0.0 – 5.4.1
NGINX Gateway Fabric 1.3.0 – 1.6.2; 2.0.0 – 2.5.1
NGINX Instance Manager 2.16.0 – 2.21.1
NGINX App Protect WAF 4.9.0 – 4.16.0; 5.1.0 – 5.8.0
F5 WAF for NGINX 5.9.0 – 5.12.1

关键点:触发条件虽然需要特定的 rewrite 规则组合,但这种组合在 API 网关和反向代理场景中极为常见。任何使用 rewrite + 未命名捕获组 + 包含 ? 的替换字符串的配置都可能受影响。


四、修复方案

4.1 升级(推荐)

产品 修复版本
NGINX Open Source 1.30.1(稳定版)或 1.31.0(主线版)
NGINX Plus R36 R36 P4
NGINX Plus R32 R32 P6

4.2 临时缓解:将未命名捕获组改为命名捕获组

# 漏洞配置
rewrite ^/api/(.*)$ /internal?migrated=true;
set $original_endpoint $1;

# 缓解配置(完全消除触发路径)
rewrite ^/api/(?<path>.*)$ /internal?migrated=true;
set $original_endpoint $path;

这个缓解方案之所以有效,是因为命名捕获组走的是不同的代码路径,不会触发 is_args 状态不一致的问题。


五、AI 发现漏洞的里程碑意义

5.1 六小时 vs 十八年

depthfirst 的 AI 分析系统对 NGINX 源代码进行扫描,仅耗时 6 小时就发现了该漏洞,同时还发现了另外 4 个安全问题。而同一个漏洞,人类安全社区——包括 NGINX 核心开发者、众多安全审计团队、以及多年的模糊测试——在 18 年中从未发现。

5.2 对安全行业的启示

这个案例标志着AI 辅助代码审计正在从"辅助工具"向"独立发现者"演进

  • 规模优势:AI 可以在几小时内审查数百万行 C 代码中的所有执行路径组合,而人类审计员很难追踪跨函数的状态依赖
  • 无疲劳:AI 不会因为"这段代码已经存在 10 年,一定是安全的"而产生认知偏差
  • 系统化:AI 可以穷举所有指令组合的排列,而人类倾向于只审查"看起来可疑"的路径

但这并不意味着人类审计将被完全取代。depthfirst 的 AI 发现了漏洞,但利用链的分析和 PoC 的编写仍然需要人类安全研究员的深度参与


六、我的观点:18 年潜伏的三个教训

教训一:"久经考验"不等于"安全"

NGINX 的 rewrite 模块是互联网基础设施中被审查最充分的代码之一。但 18 年的社区审查、企业审计和模糊测试都没有发现这个漏洞。我们必须放弃"老代码 = 安全代码"的假设。

教训二:状态不一致是最危险的 bug 类别

单看每个函数都正确的代码,组合起来可能产生灾难性后果。这与并发编程中的竞态条件类似——只是更难发现,因为它发生在确定性的两阶段处理中。

教训三:AI 正在改变漏洞发现的博弈格局

当 AI 能在 6 小时内发现人类 18 年未发现的漏洞时,防御者需要重新思考代码审计的策略。不是"用 AI 替代人类",而是"用 AI 扩展人类的认知边界"。


免责声明

本文仅供网络安全技术研究和教育目的,所有漏洞信息来源于已公开披露的安全公告(CVE-2026-42945、F5 Advisory K000161019)及安全公司的官方技术报告。本文不包含任何可直接用于非法攻击的原始利用代码或攻击载荷,所有技术描述均以安全防御、漏洞修复和行业分析为导向。读者应遵守所在国家/地区的法律法规,仅将本文内容用于授权的安全测试和防御性安全研究。未经授权对他人系统进行渗透测试或攻击属于违法行为。


参考资料