资讯 Nginx map 正则堆溢出排查:r->captures 被覆盖、两遍求值算错 buffer,13 处调用点都中招(CVE-2026-42533)

2026-09-13 20:01:29

NGINX CVE-2026-42533:regex map 覆盖 r->captures,两遍求值把 buffer 长度算错

影响范围

官方公告条目为 "Buffer overflow when using map and regex",Severity: major。

  • CVE:CVE-2026-42533
  • Not vulnerable:1.31.3+ / 1.30.4+
  • Vulnerable:0.9.6 – 1.31.2
  • 漏洞自 2011 年 3 月 NGINX 0.9.6 起潜伏,约 15 年
  • 影响 NGINX Open Source、NGINX Plus、NGINX Ingress Controller
  • CVSS v4.0 9.2 Critical / v3.1 8.1
  • 修复版本:Open Source 1.30.4(stable)/ 1.31.3(mainline);NGINX Plus 37.0.3.1 或 R37 及以上
  • 发行版另有独立补丁包

根因:LEN/VALUE 两遍求值 + 共享的 r->captures

nginx 的脚本/复杂值求值引擎对含变量的字符串走两遍(two-pass):

  1. LEN pass:逐个 token 量长度,据此从 request pool 分配一块 buffer;
  2. VALUE pass:把实际数据 memcpy 进去。

这个设计的前提是变量的值在两次 pass 之间不变。但 numbered capture($1..$9)保存在每请求共享的 r->captures 数组里,本身就是可变状态。

当求值过程中遇到一个基于正则的 map 指令,例如:

map $http_x_overflow $overflow_gadget {
"~^(.+)$" $1;
}

它会跑一次新的 PCRE 匹配,直接覆盖 r->captures,并把 r->captures_data 指向 map 的输入串(请求头 / 查询串 / 请求体)。ngx_http_complex_value()ngx_http_map_find() 都不会保存/恢复 captures 状态。结果是:LEN 量的是旧捕获,VALUE 写的是被覆盖后的新捕获,缓冲区按一个大小分配、却按另一个大小填充。

两个方向都可用

方向由 location 捕获与 map 输入的相对大小决定,攻击者可控:

  • INNER > OUTER(覆盖后更大):LEN 低估 buffer,VALUE 越界写,构成堆缓冲区溢出。可覆盖相邻的 ngx_pool_cleanup_t(handler 函数指针 / data / next),连接池销毁时调用 handler(data) 获得控制流。
  • OUTER > INNER(覆盖后更小):LEN 高估 buffer,VALUE 只写入少量字节,剩下的未初始化堆内存(复用自之前分配,含 libc / heap 指针)随响应回吐,构成信息泄露,可绕过 ASLR。

两个原语串起来可实现 pre-auth RCE,PoC 在 Ubuntu 24.04 上报告 10/10 可靠。

触发三条件

  1. 某条 location / server_name / rewrite / if 用正则在同一次请求里产生 $1 / $2 捕获;
  2. 正则 map 变量在同一条被求值的 buffer 中出现在捕获引用之后;
  3. 捕获引用和 map 变量不必在同一条指令,同一 location 块里相邻的不同指令即可。

受影响的落点指令:proxy_set_headerproxy_methodproxy_passfastcgi_paramuwsgi_paramscgi_paramgrpc_set_headerreturnadd_headerrewritesetrootaliasaccess_log 等。HTTP 和 stream 两个模块都受影响。

不止一处:9 个源文件 13 个调用点

两遍 LEN/VALUE 模式不止出现在 ngx_http_complex_value,至少分布在 9 个源文件的 13 个独立调用点。每个调用点都有自己的 LEN 循环、分配和 VALUE 循环,都通过 copy_capture_len_code / copy_capture_code 读捕获,且都不保存/恢复 r->captures。研究者用 AddressSanitizer 构建 nginx 1.30.1,确认 13 处全部可触发。

修复补丁思路

nginx PR #1561,7 个 commit、17 个文件。做法不是简单在两次 pass 之间快照 r->captures,而是让 VALUE pass 本身无法越界:

  • script engine 结构体新增 end 指针,指向已分配 buffer 边界;
  • 在写操作码(copy_codecopy_var_codecopy_capture_code 及其 ngx_escape_uri 分支)前插入 ngx_http_script_check_length() 守卫;
  • 一旦要写过 end,置 e->status=500,把 e->ip 跳向 ngx_http_script_exit,并记录 "no buffer space in script copy",调用方以 HTTP 500 中止,而不是破坏 pool 堆;
  • 信息泄露方向改为按实际写入字节数(e->pos - e->buf.data)计算返回长度,而不是用被夸大的 LEN 测量值,未初始化的尾部不再回吐。

临时缓解:把 numbered capture 换成 named capture

无法立即升级时,把 maps 里的 numbered capture 改成 named capture:

# 改前
map $http_x_overflow $overflow_gadget {
"~^(.+)$" $1;
}

# 改后
map $http_x_overflow $overflow_gadget {
"~^(?.+)$" $val;
}

named capture 走 r->variables 索引,不依赖也不修改全局 r->captures

注意研究者提到的同名变体:若 map 里用了与 location 正则同名的捕获,仍可能覆盖变量条目导致同样的 LEN/VALUE 失配,所以改 named 后要确认没有重名。

其他可做的:临时打破配置组合,避免同一请求路径上叠加「正则捕获 + regex map 输出 + 动态输出/转发」;用 WAF / 限流降低特制请求到达 worker 的概率;监控 worker 异常退出,保留 core dump 与访问日志。

自查

nginx -v 看版本;检查 conf 里是否同时存在正则捕获变量、regex map 输出变量,以及 return / add_header / proxy_set_header 等动态输出转发指令。命中组合的优先升级,否则先按上面的缓解处理。

参考链接

  • nginx 官方安全公告:https://nginx.org/en/security_advisories.html
  • F5 通告 K000162097:https://my.f5.com/manage/s/article/K000162097
  • 根因分析(cyberstan):https://cyberstan.co.uk/nginx-rce/
  • PoC 仓库:https://github.com/imbas007/CVE-2026-42533
复制全文 生成海报 Nginx 安全 CVE 运维 反向代理 漏洞修复

推荐文章

程序员茄子在线接单