CVE-2026-42945 — NGINX rewrite 堆缓冲区溢出(CWE-122)¶
严重级别
CVSS v4.0: 9.2 CRITICAL · CVSS v3.1: 8.1 HIGH · 未认证远程攻击者,单个 HTTP 请求即可触发。
| 字段 | 详情 |
|---|---|
| 项目 | NGINX |
| 受影响组件 | HTTP rewrite 模块(src/http/ngx_http_script.c);Open Source 与 NGINX Plus |
| 严重级别 | HIGH |
| CVSS v4.0 | 9.2 CRITICAL (AV:N/AC:H/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N) |
| CVSS v3.1 | 8.1 HIGH (AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H) |
| CWE | CWE-122(基于堆的缓冲区溢出) |
| 受影响版本 | Open Source 0.6.27 – 1.30.0;NGINX Plus R32 – R36 |
| 修复版本 | Open Source 1.31.0 或 1.30.1;NGINX Plus R36 P4、R35 P2、R32 P6 |
| 修复日期 | 2026-05-13 |
1. 漏洞概述¶
NGINX 的 HTTP rewrite 模块存在一个堆缓冲区溢出漏洞,未经认证的远程攻击者发送一个构造好的 HTTP 请求即可触发。在禁用 ASLR 的系统上,该溢出可达成完整的远程代码执行;在其他系统上,它能稳定地使 NGINX worker 进程崩溃。该漏洞自 NGINX 0.6.27(2008 年)起就存在,已在 2026-05-13 发布的 NGINX 1.31.0 / 1.30.1 中修复。
根本原因¶
文件: src/http/ngx_http_script.c
函数: ngx_http_script_regex_end_code()
补丁提交: 2046b45aa0c6e712c216b9075886f3f26e9b4ca9
NGINX 的 rewrite 引擎通过两个阶段来构建替换后的 URI。第一阶段是长度计算阶段,计算结果所需的字节数;第二阶段是复制阶段,将最终字节写入一个按该大小在堆上分配的缓冲区。
长度计算阶段使用一个临时子引擎(le),其 is_args 标志初始为零。由于 le.is_args = 0,函数 ngx_http_script_copy_capture_len_code() 统计的是每个 PCRE 捕获组的原始(未编码)字节数,每个源字节计为一个字节。
复制阶段在主引擎(e)上运行。如果 rewrite 替换字符串中包含 ?,ngx_http_script_start_args_code() 会将 e->is_args 置为 1,表示后续内容为查询字符串。问题在于,这个标志在复制阶段处理后续指令之前从未被清零。当 set、rewrite 或 if 指令随后将捕获变量(如 $1)复制到缓冲区时,ngx_http_script_copy_capture_code() 看到 is_args = 1,便调用 ngx_escape_uri(..., NGX_ESCAPE_ARGS) 对每个 URI 不安全字节做百分号编码,将其扩展为 %XX(三个字节)。缓冲区却只按原始字节数分配,于是复制操作溢出缓冲区,溢出量最多为 2 × N 字节,其中 N 是捕获内容中可被编码的字节数。
最简触发配置如下:
location ~ ^/api/(.*)$ {
rewrite ^/api/(.*)$ /internal?migrated=true; # 设置 e->is_args = 1
set $original_endpoint $1; # 复制阶段在此发生溢出
}
该修复仅在 ngx_http_script_regex_end_code() 开头添加了一行代码:
--- a/src/http/ngx_http_script.c
+++ b/src/http/ngx_http_script.c
@@ -1202,6 +1202,7 @@ ngx_http_script_regex_end_code(ngx_http_script_engine_t *e)
r = e->request;
+ e->is_args = 0;
e->quote = 0;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
它在每次 rewrite 替换处理结束时把 e->is_args 重置为零,让后续指令的长度计算阶段和复制阶段从相同的基准状态出发,不再出现意外的 URI 编码膨胀和随之而来的大小不匹配。
2. 漏洞环境¶
复现环境是一个 Docker Compose 栈,借助构建参数从同一份 Dockerfile 构建两个 NGINX 容器。两者都从上游 NGINX 源码编译,并启用了 AddressSanitizer(-fsanitize=address -fno-omit-frame-pointer -g -O1),这样任何堆溢出都会生成带符号信息的回溯,直接指向出问题的函数。
| 容器 | 角色 | 端口 |
|---|---|---|
cve-2026-42945-nginx |
存在漏洞的 NGINX 1.30.0(攻击目标) | 127.0.0.1:19321 → 80 |
cve-2026-42945-nginx-patched |
已修复的 NGINX 1.30.1(鉴别器) | 127.0.0.1:19322 → 80 |
两个容器均以 root 身份运行 NGINX master 进程(PID 1);worker 进程降权至非特权用户 nobody:nogroup。漏洞容器固定使用 NGINX 1.30.0 提交 6e14e954aaacce9a433d9b07b4653809c7594ab8;已修复容器使用 1.30.1 提交 9a503b1317c283e1fd0f27008428ea441c1ac9ee。
环境文件可通过 env.zip 下载。单个文件:env/Dockerfile、env/docker-compose.yml、env/config/nginx.conf、env/config/entrypoint.sh。
启动环境:
确认运行的是存在漏洞的版本,且双指令配置已就绪:
# 两个健康检查端点均应返回 "alive"
curl -fsS http://127.0.0.1:19321/healthz
curl -fsS http://127.0.0.1:19322/healthz
# 确认容器和进程状态
docker compose -f env/docker-compose.yml ps
docker exec cve-2026-42945-nginx ps -eo pid,user,args | grep 'worker process'
# 确认无预存的 ASan 报告(干净基线)
docker exec cve-2026-42945-nginx sh -c 'ls /tmp/asan.log* 2>/dev/null || echo "clean: no asan reports"'
docker exec cve-2026-42945-nginx grep -c "exited on signal" /usr/local/nginx/logs/error.log
在干净基线上,后两条命令应分别输出 clean: no asan reports 和 0。
nginx.conf 中包含触发位置块 ~ ^/api/(.*)$,其中有 rewrite ... /internal?migrated=true; 指令,后跟 set $original_endpoint $1;。AddressSanitizer 配置了 ASAN_OPTIONS log_path=/tmp/asan.log,让以 nobody 身份运行的 worker 进程能把崩溃报告写入全局可写的 /tmp 目录。entrypoint.sh 在每次容器启动时截断错误日志并删除所有已有的 /tmp/asan.log.* 文件,于是每次重启都从干净的基线开始。
3. 如何利用¶
利用程序向 ~ ^/api/(.*)$ 位置块发送单个未经认证的 HTTP GET 请求,捕获载荷全部由 + 字符组成。+ 是 URI 不安全字符,会被百分号编码为 %2B(三个字节)。发送 8000 个 + 后,复制阶段试图把 24000 字节(8000 × 3)写入一个 8000 字节的堆缓冲区,溢出 16000 字节,worker 随即崩溃。
利用文件可通过 exploit.zip 下载。单个文件:exploit/run.sh。
步骤¶
第一步: 确认环境正在运行,且基线 worker 处于存活状态。
预期输出:alive。记录当前 worker 的 PID:
第二步: 运行利用脚本。
该脚本使用 --path-as-is 选项发出 GET /api/<8000 '+'> 请求,使 + 字符原样到达服务器。预期客户端输出:
[*] Target: http://127.0.0.1:19321/api/<8000 '+'>
[*] Sending single crafted request...
curl: (52) Empty reply from server
[*] http_code=000 curl_exit_pending
[*] curl exit code: 52
连接重置(curl: (52)、http_code=000)与 worker 在请求处理中途崩溃的表现一致。但这只是客户端的观测,不能当作证明,证明要到服务端独立读取。
第三步: 从服务端验证崩溃。
读取 NGINX master 日志,确认 worker 被信号 6(SIGABRT,由 AddressSanitizer 触发)杀死:
预期输出:
确认新 worker 已替换崩溃的 worker(在已验证的运行中 PID 从 17 变为 61):
读取崩溃 worker 写入的 AddressSanitizer 堆缓冲区溢出报告:
已验证运行中的报告显示:
==17==ERROR: AddressSanitizer: heap-buffer-overflow ... WRITE of size 1
#0 ngx_escape_uri src/core/ngx_string.c:1689
#1 ngx_http_script_copy_capture_code src/http/ngx_http_script.c:1399
#2 ngx_http_rewrite_handler src/http/modules/ngx_http_rewrite_module.c:180
...
0xffff9d607040 is located 0 bytes to the right of 8000-byte region
allocated by ... ngx_http_script_complex_value_code src/http/ngx_http_script.c:1778
回溯帧 #1 ngx_http_script_copy_capture_code → #0 ngx_escape_uri 正是 e->is_args = 0 补丁所修复的代码路径。报告里溢出的堆区域为 8000 字节,恰好等于原始 + 捕获的大小,证实了长度计算阶段与复制阶段之间的大小不匹配。
上述证据由 NGINX worker 进程内部的 AddressSanitizer 插桩独立产生,与利用脚本无关。利用脚本只用 curl 发送一个 HTTP 请求,它不读取 /tmp/asan.log.*,也不执行 docker exec,无从伪造 ASan 报告或 master 日志中的信号 6 退出记录。
第四步: 对已修复的鉴别器运行相同的利用程序,以确认归因。
预期:HTTP 200、curl 退出码为 0、worker PID 不变、无 ASan 报告、错误日志中无信号退出记录。已验证的运行确认了以上全部四项。同一个请求能击垮 1.30.0,对 1.30.1 却毫发无伤,这就把崩溃明确归因于该漏洞,而不是泛泛的大请求行为。
环境清理¶
完成后,停止并移除环境:
4. 安全建议¶
修复方案¶
立即升级。 单行修复(在 ngx_http_script_regex_end_code() 中添加 e->is_args = 0;)已包含在:
- NGINX Open Source 1.31.0(主线版)或 1.30.1(稳定版)——发布日期:2026-05-13
- NGINX Plus R36 P4、R35 P2、R32 P6
只要配置中存在包含 ? 的 rewrite 替换字符串,且同一 location 块中后跟使用未命名捕获变量的 set/rewrite/if 指令,所有 NGINX Open Source 0.6.27 至 1.30.0 的安装、以及未包含补丁发行版的 NGINX Plus R32 至 R36,都存在该漏洞。
缓解措施与变通方案¶
如果无法立即升级:
- 审查 rewrite 指令。 找出替换字符串中带
?的rewrite指令所在的location块,并检查其后是否跟有引用未命名捕获($1、$2等)的set/rewrite/if指令。重构这类块以消除未命名捕获加?的组合,或重新设计 rewrite 逻辑,避开两阶段大小不匹配。 - 命名捕获不受影响。 溢出只通过未命名的 PCRE 捕获(
$1、$2)触发。把未命名捕获改成命名捕获(例如(?P<name>...)和$name)即可绕开复制阶段中的脆弱代码路径。 - 网络层过滤。 在 WAF 或上游代理处阻断或清理 URI 载荷能降低可利用性,但对内存损坏漏洞来说并不是可靠的缓解手段,升级才是正解。
参考资料¶
- NVD CVE-2026-42945 — CVE 记录、CWE、CVSS 评分
- GHSA-gcgv-v5gf-c543 — GitHub 安全公告:受影响版本与修复版本
- DepthFirst 博客:NGINX Rift — 受影响版本、根本原因概述与缓解指南
- DepthFirst 技术分析 — 详细的两阶段引擎分析与完整 RCE 利用机制
- DepthFirstDisclosures/Nginx-Rift(PoC 仓库) — 完整 RCE PoC 代码与漏洞 Docker 环境
- nginx/nginx 补丁提交 2046b45 — 单行修复:在
ngx_http_script_regex_end_code()中添加e->is_args = 0 - NGINX 更新日志(nginx.org) — 确认 1.31.0 中包含该修复(2026-05-13)
- F5 公告 K000161019 — NGINX Plus 受影响版本与修复版本范围