NGINX CVE-2026-42533 深度拆解:15 年预认证 RCE,map 指令两遍评估捕获污染攻击链
引言:一个存在了 15 年的设计级漏洞
2026 年 7 月 15 日,F5 Networks 发布了 NGINX 安全公告 K000162097,修复了 CVE-2026-42533——一个影响 NGINX Open Source 和 NGINX Plus 的堆缓冲区溢出漏洞。这不是一个普通的溢出:它自 2011 年 3 月 21 日(NGINX 0.9.6 引入 map 指令正则支持)就存在于代码中,横跨约 15 年、数十个版本。
更关键的是,这个漏洞的利用条件极为宽松:无需认证、无需客户端证书、无需用户交互,攻击者只需发送一条精心构造的 HTTP 请求即可触发。在 ASLR 被禁用或可绕过的情况下,它可以实现可靠的预认证远程代码执行。
CVSS 3.1 评分 8.1(High),CVSS 4.0 评分 9.2(Critical)。CISA 已将其纳入 SSVC 评估,技术影响评级为 "total"。
一、漏洞本质:两遍评估模型的共享状态污染
1.1 NGINX 脚本引擎的两遍评估
NGINX 的配置文件允许在变量表达式中引用正则捕获组(如 $1、$2)和 map 指令的输出变量。当 NGINX 需要求解一个包含这些引用的复杂表达式时,它在 ngx_http_complex_value()(src/http/ngx_http_script.c)中使用两遍评估模型:
┌──────────────────────────────────────────────────────┐
│ NGINX 两遍评估模型 (Two-Pass Evaluation) │
├──────────────────────────────────────────────────────┤
│ │
│ 第一遍 (LEN Pass): │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 遍历操作码 │ -> │ 求和各段 │ -> │ 得到总长度 │ │
│ │ 序列 │ │ 长度 │ │ = malloc │ │
│ └─────────┘ └─────────┘ └────┬────┘ │
│ │ │
│ ┌────────────────┘ │
│ ▼ │
│ 分配缓冲区: buf = ngx_palloc(pool, len) │
│ │ │
│ ▼ │
│ 第二遍 (VALUE Pass): │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 遍历操作码 │ -> │ 填充数据 │ -> │ 写入 buf │ │
│ │ 序列 │ │ 到缓冲区 │ │ │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ 问题: 两遍之间,共享状态 r->captures 可能被篡改! │
└──────────────────────────────────────────────────────┘- 第一遍(LEN Pass):遍历操作码序列,累加每个变量引用的值长度,得到总长度
len - 分配缓冲区:
buf = ngx_palloc(pool, len),按第一遍测量的长度分配堆内存 - 第二遍(VALUE Pass):再次遍历操作码序列,将实际数据写入
buf
这个设计的前提假设是:两遍评估之间,所有变量的值不会改变。但这个假设在引入 map 正则匹配后被打破了。
1.2 PCRE 捕获状态的共享与污染
NGINX 的正则匹配结果存储在请求结构体的 r->captures 数组中——这是一个共享的可变状态。当不同的正则操作在同一个请求处理流程中执行时,它们都读写同一个 r->captures。
漏洞的根源在于:
// 简化的漏洞触发流程
// 表达式: "$1 ${map_var}"
// LEN Pass:
// 1. 解析 $1 → 读取 r->captures[0] → 得到原始捕获长度 L1
// 2. 解析 map_var → 触发 map 正则匹配 → 覆写 r->captures!
// 3. 此时 r->captures[0] 已被 map 的正则结果替换
// 总长度 = L1 + L_map (基于原始捕获计算)
// 分配: buf = malloc(L1 + L_map)
// VALUE Pass:
// 1. 解析 $1 → 读取 r->captures[0] → 得到被污染的捕获长度 L1'
// 2. L1' 可能远大于 L1!
// 3. 将 L1' 字节的数据写入 buf → 堆溢出!当 map 指令的正则匹配在两个捕获引用之间执行时,map 的正则会覆盖 r->captures,导致第二遍评估时 $1 引用的不再是原始捕获的内容,而是 map 正则匹配后的新内容。如果新内容比原始内容长,VALUE Pass 写入的数据量就会超过 LEN Pass 测量的长度——堆缓冲区溢出。
反之,如果新内容比原始内容短,缓冲区中会残留未初始化的堆数据——信息泄露,泄露的堆指针可以用于绕过 ASLR。
二、触发条件与影响范围
2.1 漏洞触发配置模式
漏洞触发的配置条件如下:
# 必须同时存在两个条件:
# 条件 1: 正则捕获源 (产生 $1, $2 等引用)
location ~ ^/api/(.+)$ {
# $1 = "v1/users" 等
# 条件 2: 正则 map 变量在同一表达式或后续指令中被评估
# map 块定义 (在 http 块中)
# map $request_uri $backend_pool {
# ~^/api/v1/ "v1_backend";
# ~^/api/v2/ "v2_backend";
# default "default_backend";
# }
# 触发表达式: $1 在 map_var 之前被引用
# 以下任何形式都可能触发:
proxy_set_header X-Original-Path "$1 ${backend_pool}";
# 或
set $combined "$1-$backend_pool";
# 或甚至在同一 location 块的不同指令中
}
# 注意: 不限于 location 正则
# server_name、rewrite、if 的正则捕获同样有效
# stream 模块也受影响关键要点:
- 正则捕获和正则 map 变量不需要在同一个指令中——同一 location 块中的不同指令也足够触发
- HTTP 和 Stream 模块都受影响
- 攻击者通过正常的 HTTP 请求字段(URI、头部、请求体)控制溢出内容和长度
2.2 影响版本
| 版本范围 | 状态 |
|---|---|
| NGINX 0.9.6 – 1.30.3 (stable) | 受影响 |
| NGINX 1.31.2 及之前 (mainline) | 受影响 |
| NGINX Plus R33–R36 | 受影响 |
| NGINX Plus R37.0.0.1–37.0.2.1 | 受影响 |
| NGINX 1.30.4 (stable) | ✅ 已修复 |
| NGINX 1.31.3 (mainline) | ✅ 已修复 |
| NGINX Plus R36 P7+ | ✅ 已修复 |
| NGINX Plus 37.0.3.1+ | ✅ 已修复 |
重要提醒:近期的其他 NGINX CVE(CVE-2026-42945、CVE-2026-9256、CVE-2026-42055、CVE-2026-48142)不修复此漏洞。必须升级到上述指定版本。
2.3 攻击面评估
NGINX 部署在全球约 34% 的 Web 服务器上。最常见的反向代理和负载均衡器配置大量使用 map 指令处理请求头、URI 组件和查询参数——这些恰好是攻击者可控的输入源。
三、利用链构建:从信息泄露到预认证 RCE
3.1 攻击链概览
攻击者
│
├── 步骤 1: 信息泄露请求 (单条 GET)
│ ├── 发送特制 HTTP 请求
│ ├── map 正则触发, r->captures 被污染
│ ├── VALUE Pass 写入不足 → 缓冲区残留堆数据
│ └── 响应中泄露堆指针 → 绕过 ASLR
│
├── 步骤 2: 堆溢出请求 (单条 GET/POST)
│ ├── 利用步骤 1 获得的堆地址
│ ├── 构造请求使 map 正则捕获 > 原始捕获
│ ├── VALUE Pass 写入超出 LEN Pass 分配的缓冲区
│ └── 精确控制溢出内容和长度 → 覆盖关键堆元数据
│
└── 结果: 预认证远程代码执行
├── 无需认证
├── 无需客户端证书
├── 无需用户交互
└── 溢出长度无上限, 内容完全由攻击者控制3.2 信息泄露原语
当 map 正则的捕获比原始捕获短时,VALUE Pass 写入的数据少于 LEN Pass 分配的缓冲区大小。缓冲区中多出的部分包含未初始化的堆内存,其中可能包含堆指针。
# 信息泄露利用概念 (仅用于防御研究)
import requests
import struct
def leak_heap_address(target_url):
"""
通过构造特定请求触发 r->captures 污染,
使 VALUE Pass 写入不足, 响应中泄露堆指针
"""
# 构造 URI 使原始正则捕获较长
# map 正则匹配使 r->captures 被较短的内容覆盖
# 响应中 $1 的值包含未初始化堆数据
crafted_uri = "/api/AAAA...AAA" # 长原始捕获
headers = {"X-Trigger-Map": "short"} # 触发短 map 匹配
resp = requests.get(f"{target_url}{crafted_uri}", headers=headers)
# 从响应中提取泄露的堆指针
# 实际偏移取决于 NGINX 版本和编译配置
leaked_bytes = resp.headers.get("X-Original-Path", "")
# 解析泄露的地址 (示例, 实际偏移需调试)
if len(leaked_bytes) > 8:
heap_addr = struct.unpack("<Q", leaked_bytes[:8].encode())[0]
return heap_addr
return None3.3 堆溢出原语
当 map 正则的捕获比原始捕获长时,VALUE Pass 写入的数据超过 LEN Pass 分配的缓冲区大小,导致堆溢出。溢出的内容和长度完全由攻击者通过 HTTP 请求字段控制。
def trigger_overflow(target_url, heap_addr):
"""
利用泄露的堆地址, 构造溢出请求
覆盖 NGINX worker 的堆元数据实现控制流劫持
"""
# 构造 URI 使原始正则捕获较短
# map 正则匹配使 r->captures 被较长的内容覆盖
# VALUE Pass 写入超过分配的缓冲区
# 原始捕获: 短 (LEN Pass 据此分配小缓冲区)
crafted_uri = "/api/X"
# map 匹配: 长 (VALUE Pass 据此写入大量数据)
headers = {
"X-Trigger-Map": "A" * 4096, # 长 map 捕获
}
# 溢出数据中嵌入 ROP 链或 shellcode 地址
# 利用泄露的 heap_addr 精确定位目标
payload = construct_payload(heap_addr)
headers["X-Overflow-Payload"] = payload
resp = requests.get(f"{target_url}{crafted_uri}", headers=headers)
return resp.status_code3.4 利用可靠性
根据安全研究人员的测试:
- 信息泄露可在单次 GET 请求内完成 ASLR 绕过
- 溢出触发可靠率 100%(10/10 次测试均成功)
- 溢出长度无上限,内容完全由攻击者控制
- 最终实现预认证远程代码执行,可完全接管 NGINX 服务器
四、防御与修复
4.1 立即升级
# 检查当前 NGINX 版本
nginx -v
# 如果是 stable 分支, 升级到 1.30.4+
# 如果是 mainline 分支, 升级到 1.31.3+
# Ubuntu/Debian
sudo apt-get update
sudo apt-get install nginx
# CentOS/RHEL
sudo yum update nginx
# Docker 部署
docker pull nginx:1.30.4
docker-compose up -d4.2 配置审计
在无法立即升级时,审计配置中是否同时存在正则捕获和正则 map 变量的组合:
#!/bin/bash
# CVE-2026-42533 配置审计脚本
# 检查是否存在漏洞触发条件
NGINX_CONF_DIR="/etc/nginx"
VULNERABLE=0
echo "=== CVE-2026-42533 配置审计 ==="
# 检查 map 指令是否使用正则匹配
MAP_REGEX=$(grep -rn "map.*~" "$NGINX_CONF_DIR" 2>/dev/null)
if [ -z "$MAP_REGEX" ]; then
echo "[OK] 未发现正则 map 指令, 风险较低"
exit 0
fi
echo "[!] 发现正则 map 指令:"
echo "$MAP_REGEX"
echo ""
# 检查是否存在正则捕获源
echo "检查正则捕获源..."
CAPTURE_SOURCES=$(grep -rnE "location\s+~|server_name\s+~|rewrite\s+.+\(|if\s*\(" "$NGINX_CONF_DIR" 2>/dev/null)
if [ -n "$CAPTURE_SOURCES" ]; then
echo "[!] 发现正则捕获源 (可能产生 \$1, \$2 等引用):"
echo "$CAPTURE_SOURCES"
echo ""
echo "[!!!] 高危: 正则 map + 正则捕获源同时存在!"
echo "[!!!] 建议立即升级 NGINX 或重构配置"
VULNERABLE=1
fi
# 检查 $1, $2 等引用是否与 map 变量在同一表达式中
if [ "$VULNERABLE" = "1" ]; then
echo ""
echo "=== 建议修复方案 ==="
echo "1. 立即升级到 NGINX 1.30.4 (stable) 或 1.31.3 (mainline)"
echo "2. 重构配置: 避免正则捕获与正则 map 变量出现在同一表达式"
echo "3. 如 map 不需要正则, 改用精确匹配"
echo "4. 使用配置扫描工具: https://github.com/0xCyberstan/CVE-2026-42533-Config-Scanner"
fi4.3 使用社区配置扫描工具
安全研究员 0xCyberstan 发布了开源配置扫描器:
# 安装并运行配置扫描器
git clone https://github.com/0xCyberstan/CVE-2026-42533-Config-Scanner.git
cd CVE-2026-42533-Config-Scanner
# 扫描 NGINX 配置
python3 scanner.py --config-dir /etc/nginx --json
# 输出示例:
# {
# "vulnerable": true,
# "findings": [
# {
# "file": "/etc/nginx/conf.d/api.conf",
# "line": 15,
# "pattern": "location ~ ^/api/(.+)$ + map $request_uri $pool { ~^/api/ }",
# "risk": "high",
# "recommendation": "Restructure to avoid regex capture + regex map in same evaluation"
# }
# ]
# }该扫描器:
- 支持
include指令跟踪 - 处理跨指令触发场景
- 区分可利用的评估顺序与安全顺序
- 支持 JSON 输出便于 CI/CD 集成
4.4 检测与监控
# 监控 NGINX worker 异常崩溃 (可能的利用尝试)
# 在 error.log 中搜索 SIGSEGV
tail -f /var/log/nginx/error.log | grep "exited on signal 11"
# 设置告警: 短时间内多次 worker 重启
# /etc/fail2ban/jail.d/nginx-overflow.conf
[nginx-overflow]
enabled = true
filter = nginx-overflow
logpath = /var/log/nginx/error.log
maxretry = 3
findtime = 60
bantime = 3600五、与其他 NGINX CVE 的关系
2026 年 7 月前后,NGINX 密集修复了多个安全漏洞。理解它们之间的关系有助于优先级排序:
| CVE | 类型 | CVSS | 关系 |
|---|---|---|---|
| CVE-2026-42533 | map 正则堆溢出 | 8.1/9.2 | 本文重点,15 年历史,预认证 RCE |
| CVE-2026-42945 | 同引擎较弱漏洞 | 7.5 | 需要 ASLR 禁用才能利用,已被野外利用 |
| CVE-2026-42055 | HTTP/2 RCE | 9.6 | 不同组件,HTTP/2 模块 |
| CVE-2026-42530 | HTTP/3 信息泄露 | 5.3 | 不同组件,HTTP/3 模块 |
| CVE-2026-9256 | 其他修复 | - | 不修复 42533 |
| CVE-2026-48142 | 其他修复 | - | 不修复 42533 |
关键区别:CVE-2026-42945 是同一脚本引擎中的较弱漏洞,但它需要 ASLR 被禁用才能可靠利用,且在 PoC 公开后迅速被野外利用。CVE-2026-42533 不需要 ASLR 被禁用——它的信息泄露原语可以在单次 GET 请求中绕过 ASLR。
六、根因分析:设计层面的教训
6.1 共享可变状态的陷阱
这个漏洞的本质是共享可变状态(r->captures)在多次访问之间被意外修改。这是一个经典的设计级问题:
设计假设: 两遍评估之间, 变量值不变
实际行为: map 正则在两遍之间覆盖了 r->captures
结果: LEN Pass 和 VALUE Pass 对 $1 的理解不一致NGINX 的脚本引擎在设计之初没有考虑到 map 指令的正则匹配会修改 r->captures——因为 map 指令的正则支持是在 0.9.6 版本才加入的(2011 年),而两遍评估模型在此之前就已存在。
6.2 修复方案:保存与恢复
正确的修复方式是在 map 正则匹配前保存 r->captures 状态,匹配后恢复:
// 修复后的伪代码
ngx_int_t ngx_http_complex_value(ngx_http_request_t *r,
ngx_http_complex_value_t *val, ngx_str_t *value)
{
// ... LEN Pass ...
// 在 map 正则匹配前保存 captures
ngx_int_t *saved_captures = NULL;
if (r->captures) {
saved_captures = ngx_palloc(r->pool, r->captures_elts * sizeof(ngx_int_t));
ngx_memcpy(saved_captures, r->captures, r->captures_elts * sizeof(ngx_int_t));
}
// ... 执行 map 正则匹配 ...
// 恢复 captures
if (saved_captures) {
ngx_memcpy(r->captures, saved_captures, r->captures_elts * sizeof(ngx_int_t));
}
// ... VALUE Pass ...
}6.3 对安全工程师的启示
- 两遍评估是危险模式:任何 "先测量再填充" 的设计都必须保证两遍之间状态一致
- 共享可变状态需要显式管理:
r->captures作为一个共享缓冲区,任何修改它的操作都应该有保存/恢复机制 - 功能叠加引入新攻击面:map 正则支持是在两遍评估模型之后加入的,这种功能叠加没有被充分审计
- 配置扫描不能替代升级:即使配置当前不触发漏洞,未来配置变更可能引入触发条件
七、时间线
| 时间 | 事件 |
|---|---|
| 2011-03-21 | NGINX 0.9.6 引入 map 指令正则支持,漏洞引入 |
| 2026-07-15 | F5 发布安全公告 K000162097,CVE-2026-42533 公开 |
| 2026-07-15 | CISA 进行 SSVC 评估,技术影响评级 "total" |
| 2026-07-15 | NGINX 1.30.4 (stable) 和 1.31.3 (mainline) 发布 |
| 2026-07-16 | NVD 发布 CVE 详情 |
| 2026-07-20 | 配置扫描工具发布 |
| 截至本文 | PoC 未公开(研究员延迟发布以给用户修复时间) |
| 待定 | PoC 公开后预计出现大规模扫描和利用 |
总结
CVE-2026-42533 是一个教科书级别的设计级漏洞:一个合理的架构假设(两遍评估之间状态不变)在功能演进(map 正则支持)后变得不成立,而这个不一致在 15 年里没有被任何人发现。
对于安全工程师和运维团队:
- 立即升级到 NGINX 1.30.4+ 或 1.31.3+
- 审计配置中是否同时存在正则捕获和正则 map 变量
- 监控 worker 崩溃作为可能的利用尝试指标
- 关注 CISA KEV:一旦 CVE-2026-42533 被加入,说明已确认野外利用
15 年,13 个调用点,一条 HTTP 请求就能打穿。这是 2026 年最需要紧急打补丁的 NGINX 漏洞之一。

