共计 9679 个字符,预计需要花费 25 分钟才能阅读完成。
潜伏18年的定时炸弹:CVE-2026-42945漏洞深度剖析
2026年5月12日,NGINX官方发布紧急安全公告,披露了一组高危漏洞,其中最严重的CVE-2026-42945(代号「NGINX Rift」)的CVSS评分高达9.2分。这个漏洞竟然在代码库中潜伏了整整18年,影响全球超过1.3亿个网站。作为一名长期关注网络安全的从业者,这个消息让我深感震惊。今天,我们就来深度剖析这个漏洞的原理、复现方式以及应对策略。
一、漏洞概述:18年的沉默杀手
1.1 基本信息
| 项目 | 详情 |
|---|---|
| 漏洞编号 | CVE-2026-42945(代号「NGINX Rift」) |
| 漏洞类型 | 堆缓冲区溢出(Heap Buffer Overflow) |
| CVSS 4.0评分 | 9.2分(极危 Critical) |
| CVSS 3.1评分 | 8.1分(高危 High) |
| 影响模块 | ngx_http_rewrite_module |
| 影响版本 | NGINX 0.6.27 至 1.30.0 |
| 发现者 | depthfirst 安全研究团队 |
| 披露日期 | 2026年5月12日 |
| 修复版本 | NGINX 1.30.1+ |
1.2 影响规模
这个漏洞的影响范围令人触目惊心:
- 全球市场份额:NGINX占据全球Web服务器市场份额的37.2%,是当之无愧的霸主
- 受影响网站数量:超过1.3亿个网站和API网关可能受此漏洞影响
- 中国受影响情况:超过254万个暴露在互联网上的NGINX实例可能受影响,数量位居全球第二
根据最新统计数据,全球各地区受影响的NGINX实例分布如下:
| 国家/地区 | 暴露实例数 |
|---|---|
| 美国 | 5,340,011 |
| 中国 | 2,540,008 |
| 德国 | 1,871,780 |
1.3 漏洞的特殊性
这个漏洞之所以引起业界高度关注,有以下几个特殊原因:
第一,潜伏时间超乎想象。 最早于2008年被引入,在代码中存在了约18年。这意味着在过去18年里,所有使用NGINX rewrite模块的服务器都可能受到影响。
第二,利用门槛极低。 攻击者只需发送一个精心构造的HTTP请求即可触发漏洞,无需任何身份认证。
第三,危害程度极高。 从简单的服务崩溃到远程代码执行,后果涵盖多个安全级别。
第四,波及范围极广。 从个人博客到大型企业级应用,从传统服务器到Kubernetes集群,无一幸免。
二、技术原理:双重引擎的状态不一致
2.1 ngx_http_rewrite_module的作用
要深入理解这个漏洞,我们首先需要了解ngx_http_rewrite_module在NGINX中的重要作用。
ngx_http_rewrite_module是NGINX的核心模块之一,负责URL重写功能。这个模块允许服务器根据正则表达式规则修改请求的URI,是Web服务器配置中最常用的模块之一。
在实际生产环境中,rewrite模块广泛应用于以下场景:
URL规范化:将用户访问的URL重写为标准格式,例如将example.com/about重写到example.com/about/,确保搜索引擎正确收录。
伪静态实现:将动态URL转换为搜索引擎友好的静态URL形式,例如将example.com/article.php?id=123转换为example.com/article-123.html。
API网关路由:在微服务架构中,将外部请求路由到内部服务,例如将example.com/api/v1/users重写到后端服务。
移动端适配:根据用户代理字符串将请求重定向到移动端或桌面端页面。
A/B测试:将不同比例的流量分发到不同版本的应用。
正是因为这些广泛的应用场景,几乎每一个NGINX配置文件中都会用到rewrite指令,这也使得这个漏洞的影响范围如此广泛。
2.2 漏洞根因分析
2.2.1 核心缺陷定位
漏洞位于src/http/ngx_http_script.c文件的ngx_http_script_regex_replace函数中。问题的本质是NGINX脚本引擎在处理变量重组时,预判阶段对目标内存长度的估算与执行阶段实际写入时的内存分配之间存在严重不对等。
具体来说,这是一个「计算逻辑脱节」问题。NGINX的脚本引擎对重写目标进行两次遍历,两次遍历使用的上下文状态不一致,导致内存分配大小与实际需要的大小产生巨大偏差。
2.2.2 is_args标志位管理错误
理解这个漏洞的关键在于理解is_args标志位的作用。
在URL处理中,问号(?)是区分URI路径和查询参数的界限符。当替换字符串中包含问号时,NGINX需要正确处理这个边界。
然而,在双重遍历机制中:
第一遍(长度分配阶段):引擎使用一个「完全清零」的子引擎结构体,is_args标志位默认为0。此时计算捕获变量长度时,以「原始字节数」计算,完全忽略了后续可能发生的转义膨胀。
第二遍(数据写入阶段):这个阶段交还给保留完整上下文的「主引擎」。当检测到替换字符串中包含问号时,is_args标志位正确设置为1。此时引擎会调用ngx_escape_uri函数进行强制转义,使用NGX_ESCAPE_ARGS模式。
问题就出在这里:分配阶段以为需要N字节,但写入阶段实际需要3N字节(因为转义膨胀),导致缓冲区溢出。
2.3 触发条件详解
漏洞并非在所有rewrite配置下都能触发,必须同时满足以下三个条件:
条件一:使用未命名的PCRE正则捕获
这是最常见的捕获方式,使用$1、$2等变量引用正则表达式中的捕获组。例如:
rewrite ^/api/v1/(.*)$ /internal.php?route=$1;
条件二:替换字符串包含问号(?)
问号在URL中有特殊含义,用于分隔路径和查询参数。当替换字符串包含问号时,会触发URI与查询参数的分界处理逻辑变化。
条件三:同作用域内紧跟另一个触发重评估的指令
例如set、if、rewrite等指令。这些指令会触发NGINX重新评估变量,导致双重遍历机制被执行。
2.4 致命算术冲突示例
让我们通过一个具体的例子来说明漏洞是如何工作的:
假设攻击者发送一个包含1000个连续百分号(%)的请求路径。
在第一遍遍历时,子引擎计算$1的长度为1000字节(原始输入长度),于是分配1000字节的缓冲区。
在第二遍遍历时,主引擎检测到替换字符串中的问号,开始执行URL转义。每个百分号(%)都会被转义为%25(3个字节)。1000个百分号变成了3000个字节,但缓冲区只有1000字节。
结果:写入3000字节到1000字节的缓冲区,溢出2000字节。
更危险的是,这溢出的2000字节完全由攻击者可控,可以写入任意数据,实现「可控的内存破坏」。
三、漏洞利用分析
3.1 拒绝服务攻击(DoS)
这是最容易实现、也是默认配置下最可靠的攻击方式。
攻击流程
- 攻击者发送包含大量膨胀字符的HTTP请求
- 精心构造的路径触发堆缓冲区溢出
- 溢出数据破坏ngx_pool_t或glibc ptmalloc的内存元数据
- NGINX检测到堆损坏,抛出SIGSEGV或SIGABRT信号
- Worker进程崩溃
- Master进程检测到Worker崩溃,尝试重启
- 新启动的Worker再次接收恶意请求,再次崩溃
- 系统进入Crash Loop,服务彻底瘫痪
实际影响
在Alma Linux 8/9/10系统上的实测表明,这种攻击可以稳定触发服务崩溃。由于Master进程会不断重启Worker,系统会产生大量的崩溃日志和进程创建开销,即使攻击停止,系统也需要一段时间才能恢复正常。
更糟糕的是,如果攻击者持续发送请求,系统将永远无法恢复正常服务,造成永久性的拒绝服务。
3.2 信息泄露攻击
通过精确控制溢出数据,攻击者可能读取堆内存中的敏感信息。
可能泄露的数据类型
- 会话令牌和认证凭证
- API密钥和数据库连接字符串
- 其他用户的私人数据
- 服务器配置信息
- 内存中的任意数据
攻击难度
信息泄露需要更精确地控制溢出数据的位置和内容,攻击难度比DoS更高。但在某些特定配置下,攻击者可能利用内存布局的可预测性来提取敏感信息。
3.3 远程代码执行(RCE)
这是漏洞利用的最高级别,但在默认配置下较难实现。
实现前提条件
RCE需要满足以下条件:
关闭ASLR:系统的ASLR(地址空间布局随机化)保护必须被禁用。这需要攻击者具有系统管理员权限或能够执行特定的操作来修改系统配置。
sysctl kernel.randomize_va_space=0
掌握Heap Grooming技术:攻击者需要预先布局堆内存,使目标数据位于可预测的位置。
精确控制溢出内容:攻击者需要精确控制$1捕获变量的内容构成,使其正好覆盖关键函数指针。
绕过安全机制:需要绕过NX(不可执行内存)等操作系统级别的安全保护。
利用技术链
完整的RCE利用通常包括以下步骤:
- Heap Grooming(堆风水):预先布局堆内存,创建可预测的内存布局
- 溢出覆盖:利用缓冲区溢出覆盖关键回调函数指针
- 控制流劫持:将程序执行重定向到攻击者控制的代码
- 代码执行:执行shellcode或ROP Chain获取系统控制权
风险评估
在默认开启ASLR的现代Linux系统上,RCE的成功率极低。但考虑到漏洞的严重性,任何可能实现RCE的漏洞都应该被视为最高优先级安全问题来处理。
3.4 Kubernetes ingress-nginx的特殊风险
对于使用Kubernetes的企业用户,需要特别关注ingress-nginx的安全问题。
问题根源
ingress-nginx控制器的Docker镜像中静态嵌入了特定版本的NGINX二进制文件。这意味着:
- 宿主机的NGINX版本更新无法影响到容器内的NGINX
- 即使Kubernetes节点升级了NGINX包,Pod内运行的仍然是易受攻击的版本
- 需要更新ingress-nginx控制器镜像本身才能修复漏洞
受影响版本
Kubernetes ingress-nginx控制器使用的镜像内置了NGINX 1.27.1,这是一个受影响的版本。
处置方案
推荐方案:迁移至Kubernetes Gateway API,这是Kubernetes官方推荐的下一代Ingress标准,不依赖NGINX。
临时方案:使用社区维护的fork镜像(如Forkline项目),这些镜像已经合并了安全补丁。
过渡方案:使用已合并上游补丁1.30.1+的分支镜像,但需要从社区获取而非官方渠道。
四、危险配置模式示例
在实际环境中,有两种最常见的危险配置模式需要特别警惕。
4.1 PHP前端控制器模式
这是最常见的配置模式,广泛存在于各种PHP应用中:
location / {
rewrite ^/(.*)$ /index.php?$1 break;
rewrite ^/admin/(.*)$ /admin/index.php?$1 break;
}
这种配置看起来很普通,但当请求路径匹配到$1的捕获内容时,如果内容包含特殊字符,就可能触发漏洞。
4.2 API网关路由模式
在微服务架构中,这种配置也很常见:
location /api/v1/ {
rewrite ^/api/v1/(.*)$ /$1?$args break;
if ($request_method = POST) {
proxy_pass http://backend:8080;
}
}
这里的连续rewrite指令和if语句形成了完整的漏洞触发条件。
4.3 其他高风险场景
旧版WordPress固定链接:
location / {
try_files $uri $uri/ /index.php?$request_uri;
}
rewrite ^/archives/(\d+)$ /index.php?p=$1 last;
CodeIgniter框架路由:
if (!-e $request_filename) {
rewrite ^/(.*)$ /index.php?/$1 last;
}
五、修复方案
5.1 核心修复:软件升级
各版本升级目标
| 用户类型 | 当前版本 | 目标版本 |
|---|---|---|
| NGINX Open Source(稳定分支) | 0.6.27-1.30.0 | ≥ 1.30.1 |
| NGINX Open Source(主线分支) | 任意版本 | ≥ 1.31.0 |
| NGINX Plus R32 | 任意版本 | R32 P6 |
| NGINX Plus R35 | 任意版本 | R35 P2 |
| NGINX Plus R36 | 任意版本 | R36 P4 |
| Ubuntu 24.04 LTS | 任意版本 | nginx-1.24.0-2ubuntu7.8 |
| AlmaLinux/RHEL | 任意版本 | nginx-1.14.1-9.el8.10.alma.1(及对应版本) |
重要提醒
升级后必须执行systemctl restart nginx,而不是systemctl reload nginx。原因如下:
- reload只会在不中断连接的情况下重新加载配置
- reload不会替换内存中正在运行的NGINX二进制文件
- 旧版二进制文件仍在内存中运行,漏洞依然存在
- restart会完全停止并重新启动NGINX,加载新版二进制文件
Debian/Ubuntu升级命令
sudo apt update
sudo apt upgrade nginx
sudo systemctl restart nginx
CentOS/RHEL/Rocky Linux升级命令
sudo yum update nginx
# 或者
sudo dnf update nginx
sudo systemctl restart nginx
验证升级结果
# 检查版本
nginx -v
# 应输出:nginx version: nginx/1.30.1
# 检查配置
nginx -t
# 应输出:nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
# nginx: configuration file /etc/nginx/nginx.conf test is successful
# 检查服务状态
systemctl status nginx
# 验证服务响应
curl -I http://localhost/
5.2 Kubernetes环境处理
检查当前版本
kubectl exec -n ingress-nginx -- /nginx-ingention-controller --version
推荐的迁移路径
短期(紧急修复):
- 切换到社区维护的patched镜像
- 配置WAF规则阻断恶意请求
- 限制ingress-nginx的外部访问
中期(标准升级):
- 等待官方发布修复后的控制器版本
- 测试新版本与现有配置兼容性
- 制定升级计划
长期(架构演进):
- 评估迁移到Gateway API的可行性
- 制定应用改造计划
- 逐步迁移并验证功能
5.3 临时缓解措施
如果无法立即升级,可以采用以下临时缓解措施。但必须明确:这些措施只是降低被利用风险,不能替代升级到安全版本。
方案一:使用命名捕获替代未命名捕获
这是最推荐的缓解方式,可以从根本上消除漏洞触发条件:
# 脆弱配置(存在风险)
location /api/ {
rewrite ^/api/(.*)$ /v2/api.php?query=$1;
set $endpoint "api_v2";
}
# 安全配置(推荐)
location /api/ {
rewrite ^/api/(?.*)$ /v2/api.php?query=$apipath;
set $endpoint "api_v2";
}
命名捕获(?pattern)与未命名捕获(pattern)的根本区别在于:命名捕获会将捕获内容存储在命名变量中,而不是$1、$2等数字变量。这避免了双重遍历机制的使用,从根本上消除了漏洞触发条件。
方案二:打破指令链
移除替换字符串中的问号符号,或拆解紧跟的set/if指令:
# 脆弱配置
location / {
rewrite ^/old/(.*)$ /new/$1? break;
set $my_var $1;
}
# 安全配置(移除问号)
location / {
rewrite ^/old/(.*)$ /new/$1 break;
set $my_var $1;
}
# 或者拆解配置
location /new/ {
# 独立的location块,避免指令链
}
location /old/ {
rewrite ^/old/(.*)$ /new/$1 break;
}
方案三:限制URI长度
在nginx.conf中添加限制,减小潜在溢出规模:
http {
# 限制请求URI长度
client_header_buffer_size 1k;
large_client_header_buffers 4 8k;
# 限制请求体大小
client_max_body_size 10m;
}
六、检测与监控
6.1 日志特征检测
崩溃日志识别
在NGINX错误日志中监控以下模式:
worker process exited on signal 11
worker process exited on signal 6
worker process exited on signal 7
- Signal 11 (SIGSEGV):段错误,通常表示内存访问违规
- Signal 6 (SIGABRT):主动终止,通常由assert失败触发
- Signal 7 (SIGBUS):总线错误,通常表示内存对齐问题
日志分析命令
# 查看最近的崩溃日志
tail -f /var/log/nginx/error.log | grep "signal"
# 统计崩溃次数
grep -c "signal" /var/log/nginx/error.log
# 分析崩溃时间分布
grep "signal" /var/log/nginx/error.log | awk '{print $1, $2}' | sort | uniq -c
6.2 流量异常识别
攻击特征荷载
恶意请求通常具有以下特征:
- 大量未编码的百分号:如
%%%%...(连续10个以上的百分号) - 异常的字符序列:如
%+%+%+%+或&&&&&&&& - 超长URI请求:正常请求的URI长度通常不超过几百字节
- 来源IP异常集中:同一IP发送大量异常请求
WAF规则示例
# 使用ngx_http_referer_module(基础防护)
if ($request_uri ~* "%[0-9a-f]{2}") {
return 403;
}
更完善的WAF规则建议使用ModSecurity或使用云WAF服务。
流量监控命令
# 监控高频访问的URI
awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
# 检测异常URI模式
awk '$7 ~ /%[0-9a-f]{10,}/ {print $0}' /var/log/nginx/access.log
# 分析可疑IP
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
6.3 ASLR基线检查
ASLR是防止RCE的重要保护机制,应确保其处于开启状态:
# 检查ASLR状态
sysctl kernel.randomize_va_space
# 期望返回值:2(完全开启)
# 返回值含义:
# 0 = 关闭
# 1 = 随机化堆
# 2 = 随机化堆、栈、共享库
如果返回0,应立即修复:
# 临时启用
sudo sysctl -w kernel.randomize_va_space=2
# 永久启用(编辑/etc/sysctl.conf)
echo "kernel.randomize_va_space = 2" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
七、漏洞关联与周边风险
7.1 同批次披露的相关漏洞
F5在同期还修复了多个相关的内存破坏和逻辑错乱漏洞,它们共同构成了NGINX历史上最严重的安全更新之一。
这些漏洞虽然单独来看可能只是中危或高危,但组合在一起可能形成更严重的安全威胁。建议管理员在修复CVE-2026-42945的同时,也应该评估是否需要部署完整的补丁包。
7.2 历史同类漏洞回顾
回顾NGINX的历史,我们可以发现rewrite模块一直是漏洞的高发区:
- CVE-2013-2028:ngx_http_mp4_module模块的栈缓冲区溢出
- CVE-2017-7529:整数溢出导致敏感信息泄露
- CVE-2019-9516:HTTP/2拒绝服务漏洞
这些漏洞提醒我们,作为最流行的Web服务器之一,NGINX的安全问题会影响到互联网上很大比例的网站。
八、我的感悟
作为一名长期与服务器打交道的从业者,这个漏洞给我带来了几点深刻思考。
第一,「老代码不等于安全代码」。 18年的潜伏期意味着这个漏洞可能影响了无数生产环境。我们常常认为开源软件经过社区长时间检验更加安全,但CVE-2026-42945恰恰相反——正是这种信任让漏洞得以长期隐藏。建议任何软件,长时间不更新并不意味着安全,定期审计和更新同样重要。
第二,安全与便利的平衡永远是个难题。 rewrite模块的灵活配置是其广受欢迎的原因,但也正是这种灵活性导致了漏洞的触发条件复杂性。作为运维人员,我们需要在功能需求和安全风险之间找到平衡点。
第三,纵深防御的重要性再次凸显。 即使漏洞无法完全避免,我们可以通过ASLR、WAF、监控告警等多层防护来降低风险。单点防护终究不可靠,系统性的安全架构才是王道。
第四,对Kubernetes环境需要格外关注。 ingress-nginx的嵌入式设计虽然便于部署,但也带来了版本更新的滞后性。云原生时代,我们不能只关注应用层的安全,基础设施的安全同样不容忽视。
第五,应急响应能力是团队的核心竞争力。 这个漏洞从披露到修复的窗口期非常关键。一个拥有成熟应急响应机制的团队,能够在第一时间完成漏洞评估、修复方案制定和实施,将风险降到最低。
写在最后
CVE-2026-42945的披露再次提醒我们:网络安全是一场永无止境的战斗。作为技术人员,我们能做的就是快速响应、持续学习、不断完善自己的安全知识体系。
如果你正在使用NGINX,请立即检查你的配置是否存在危险模式,尽快升级到安全版本。安全无小事,防微杜渐方能行稳致远。
在这个漏洞的阴影下,我更加坚信:安全的本质不是追求绝对的安全,而是将风险控制在可接受的范围内。这需要我们具备全局视野、持续学习和快速行动的能力。
愿每一个服务器都能远离漏洞的威胁,愿每一位技术人都能在这个充满挑战的领域中保持警惕、不断进步。
记于CVE-2026-42945漏洞披露之际