
自查
- 影响范围:NGINX Open Source
0.6.27 ~ 1.30.0,官方修复边界是1.30.1+或1.31.0+。 - 先自查:
nginx -v,再扫配置里有没有rewrite + $1/$2 + ?,后面还接rewrite/if/set。 - 要不要升级:只要版本低于修复边界,或者命中上面的危险配置,先升级。
- 有没有中招:生产上如果已经出现 worker 异常退出、
core dumped、无故重启,先按受影响处理;不确定就去隔离环境验证。 - 脚本覆盖:Debian/Ubuntu、RHEL/CentOS/Rocky/Alma/Oracle、Amazon Linux、SLES、Alpine;Fedora、Arch、openSUSE 这类不在 nginx.org 官方二进制源支持列表里的系统,脚本会停下来让你手动确认。
自查脚本
bash
nginx -v
sudo nginx -T > /tmp/nginx-all.conf 2>/tmp/nginx-test.log
rg -n 'rewrite\s+.*\$\d+.*\?' /tmp/nginx-all.conf
修复步骤
- 先备份
/etc/nginx。 - 根据系统类型安装基础依赖。
- 添加 nginx 官方 mainline 源和签名 key。
- 用当前系统的包管理器安装 nginx。
nginx -v、nginx -t通过后再重启。
一键修复脚本
bash
#!/bin/sh
set -eu
NGINX_KEY_URL="https://nginx.org/keys/nginx_signing.key"
NGINX_APK_KEY_URL="https://nginx.org/keys/nginx_signing.rsa.pub"
if [ "$(id -u)" -ne 0 ]; then
echo "请用 root 运行:sudo sh $0"
exit 1
fi
if [ ! -r /etc/os-release ]; then
echo "无法读取 /etc/os-release,先手动确认发行版"
exit 1
fi
. /etc/os-release
BACKUP_DIR="/root/nginx-backup-$(date +%F-%H%M%S)"
mkdir -p "$BACKUP_DIR"
if [ -d /etc/nginx ]; then
cp -a /etc/nginx "$BACKUP_DIR/"
fi
restart_nginx() {
if command -v systemctl >/dev/null 2>&1 && [ -d /run/systemd/system ]; then
systemctl restart nginx
return
fi
if command -v rc-service >/dev/null 2>&1; then
rc-service nginx restart || rc-service nginx start
return
fi
if command -v service >/dev/null 2>&1; then
service nginx restart || service nginx start
return
fi
nginx -s reload 2>/dev/null || nginx
}
version_ge() {
awk -v v="$1" -v min="$2" '
BEGIN {
split(v, a, "."); split(min, b, ".");
for (i = 1; i <= 3; i++) {
a[i] += 0; b[i] += 0;
if (a[i] > b[i]) exit 0;
if (a[i] < b[i]) exit 1;
}
exit 0;
}'
}
verify_nginx() {
nginx -t
restart_nginx
nginx -v
INSTALLED="$(nginx -v 2>&1 | sed -n 's#.*nginx/##p' | sed 's/[^0-9.].*$//')"
if version_ge "$INSTALLED" "1.30.1"; then
echo "版本检查通过:nginx/$INSTALLED"
else
echo "版本仍低于 1.30.1,请检查包来源或发行版 backport 状态:nginx/$INSTALLED"
exit 1
fi
}
setup_apt() {
OS="$1"
KEYRING_PACKAGE="$2"
CODENAME="${3:-${VERSION_CODENAME:-}}"
if [ -z "$CODENAME" ] && command -v lsb_release >/dev/null 2>&1; then
CODENAME="$(lsb_release -cs)"
fi
if [ -z "$CODENAME" ]; then
echo "无法识别发行版代号"
exit 1
fi
apt-get update
apt-get install -y curl gnupg ca-certificates lsb-release "$KEYRING_PACKAGE"
install -d -m 0755 /usr/share/keyrings
curl -fsSL "$NGINX_KEY_URL" | gpg --dearmor >/usr/share/keyrings/nginx-archive-keyring.gpg
chmod 0644 /usr/share/keyrings/nginx-archive-keyring.gpg
cat >/etc/apt/sources.list.d/nginx.list <<EOF
deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] https://nginx.org/packages/mainline/${OS} ${CODENAME} nginx
EOF
cat >/etc/apt/preferences.d/99nginx <<'EOF'
Package: *
Pin: origin nginx.org
Pin: release o=nginx
Pin-Priority: 900
EOF
apt-get update
apt-cache policy nginx
apt-get install -y nginx
}
setup_rhel_family() {
PM="yum"
command -v dnf >/dev/null 2>&1 && PM="dnf"
"$PM" install -y yum-utils ca-certificates curl
cat >/etc/yum.repos.d/nginx.repo <<'EOF'
[nginx-stable]
name=nginx stable repo
baseurl=https://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=1
enabled=0
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
[nginx-mainline]
name=nginx mainline repo
baseurl=https://nginx.org/packages/mainline/centos/$releasever/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
EOF
"$PM" module disable -y nginx >/dev/null 2>&1 || true
"$PM" install -y nginx
}
setup_amazon() {
PM="yum"
command -v dnf >/dev/null 2>&1 && PM="dnf"
"$PM" install -y yum-utils ca-certificates curl
if [ "${VERSION_ID:-}" = "2023" ]; then
MAINLINE_URL='https://nginx.org/packages/mainline/amzn/2023/$basearch/'
STABLE_URL='https://nginx.org/packages/amzn/2023/$basearch/'
else
MAINLINE_URL='https://nginx.org/packages/mainline/amzn2/$releasever/$basearch/'
STABLE_URL='https://nginx.org/packages/amzn2/$releasever/$basearch/'
fi
cat >/etc/yum.repos.d/nginx.repo <<EOF
[nginx-stable]
name=nginx stable repo
baseurl=${STABLE_URL}
gpgcheck=1
enabled=0
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
priority=9
[nginx-mainline]
name=nginx mainline repo
baseurl=${MAINLINE_URL}
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
priority=9
EOF
"$PM" install -y nginx
}
setup_sles() {
zypper --non-interactive install curl ca-certificates gpg2
curl -fsSL -o /tmp/nginx_signing.key "$NGINX_KEY_URL"
rpmkeys --import /tmp/nginx_signing.key
zypper --non-interactive removerepo nginx-mainline >/dev/null 2>&1 || true
zypper --non-interactive addrepo --gpgcheck --type yum --refresh --check \
'https://nginx.org/packages/mainline/sles/$releasever_major' nginx-mainline
zypper --non-interactive refresh
zypper --non-interactive install nginx
}
setup_alpine() {
apk add --no-cache openssl curl ca-certificates
ALPINE_VER="$(grep -o '^[0-9]\+\.[0-9]\+' /etc/alpine-release)"
REPO="@nginx https://nginx.org/packages/mainline/alpine/v${ALPINE_VER}/main"
grep -qxF "$REPO" /etc/apk/repositories || echo "$REPO" >>/etc/apk/repositories
curl -fsSL -o /etc/apk/keys/nginx_signing.rsa.pub "$NGINX_APK_KEY_URL"
apk update
apk add nginx@nginx
}
case "${ID:-}" in
debian)
setup_apt "debian" "debian-archive-keyring" "${1:-}"
;;
ubuntu)
setup_apt "ubuntu" "ubuntu-keyring" "${1:-}"
;;
rhel|centos|rocky|almalinux|ol|oracle)
setup_rhel_family
;;
amzn)
setup_amazon
;;
sles|sled|sles_sap)
setup_sles
;;
alpine)
setup_alpine
;;
fedora|arch|manjaro|opensuse-leap|opensuse-tumbleweed|opensuse)
echo "当前系统 ${ID:-unknown} 不在 nginx.org 官方二进制源支持列表里。"
echo "请用系统仓库升级后手动确认 nginx -v 是否 >= 1.30.1,或改用官方 Docker 镜像。"
exit 1
;;
*)
echo "暂未识别系统 ID=${ID:-unknown},请按 nginx 官方文档手动配置 mainline 源。"
exit 1
;;
esac
verify_nginx
if command -v apt-cache >/dev/null 2>&1; then
apt-cache policy nginx || true
elif command -v rpm >/dev/null 2>&1; then
rpm -qi nginx || true
elif command -v apk >/dev/null 2>&1; then
apk info -v nginx || true
fi
echo "完成,备份目录:$BACKUP_DIR"
Nginx 漏洞编号是 CVE-2026-42945,也有人叫它 NGINX Rift。
它的问题不在控制台,也不在某个后台接口,而是在请求处理路径里的 ngx_http_rewrite_module。公网请求只要能打到命中的 rewrite 配置,就有机会触发 worker 进程里的堆缓冲区溢出。NVD 里 F5 CNA 给的是 CVSS v4.0 9.2,v3.1 是 8.1;nginx.org 的安全公告页把它列成 medium,但受影响版本范围写得很直接:0.6.27 到 1.30.0,修复边界是 1.30.1+ 或 1.31.0+。
这篇就不写成新闻稿了。我把我真正关心的几个点捋一下:到底什么配置会中、怎么在自己的环境里复现风险、Debian 上为什么看版本号容易误判、最后我是怎么升级到 nginx 官方 mainline 1.31.x 的。
触发点不是所有 rewrite,而是这个组合
这次漏洞卡在一个很具体的配置模式上。
按 NVD 的描述,满足下面几件事才危险:
- 使用
ngx_http_rewrite_module里的rewrite指令。 - 正则里用了未命名 PCRE 捕获,比如
$1、$2。 - 替换字符串里带了
?。 - 这个
rewrite后面又跟着rewrite、if或set指令。
例子大概长这样:
nginx
server {
listen 8080;
location / {
rewrite ^/users/([0-9]+)/profile/(.*)$ /profile.php?id=$1&tab=$2 last;
set $rewrite_marker 1;
return 200 "ok\n";
}
}
这里最刺眼的是 $1、$2 和替换目标里的 ?。正常看它只是把路径改写到 PHP 参数里,这类配置在老项目、网关、CMS 迁移规则里并不少见。
问题就在这:Nginx 脚本引擎会先算目标 buffer 需要多长,再把内容 copy 进去。DepthFirst 的分析里提到,当替换字符串里出现 ? 时,主执行引擎会进入 args escape 相关状态;但长度计算那一轮用的是一个新的子引擎,它看到的状态不一致。于是长度按原始 capture 算,真正 copy 时又按 query args 规则转义,某些字符会膨胀,最后写出分配好的 heap buffer。
也就是说,这不是"正则写得丑"那么简单。
它是长度计算和实际写入的 escaping 假设不一致。
我会先扫配置,而不是先跑 PoC
公开 PoC 已经有了,甚至有 RCE 复现仓库。这里我不贴直接拿去打 shell 的命令,没必要。对大多数维护自己服务器的人来说,第一步应该是确认配置是否命中触发模式,而不是拿 payload 去怼生产。
先看版本:
bash
nginx -v
nginx -V 2>&1 | head
如果是 Open Source 1.30.1+ 或 1.31.0+,这条 CVE 已经在官方修复边界内。否则继续看配置。
先粗扫所有 rewrite:
bash
sudo nginx -T > /tmp/nginx-all.conf 2>/tmp/nginx-test.log
rg -n 'rewrite\s+.*\$\d+.*\?' /tmp/nginx-all.conf
这个命令不是完美检测器,但很适合第一轮排查。它找的是 rewrite 里同时出现未命名捕获引用和问号的地方。
然后再看这些命中的 rewrite 后面,同一层级里有没有继续跟 rewrite、if 或 set。这个判断靠纯正则不太稳,最好打开配置人工看一遍。尤其是很多人会把公共片段拆到 /etc/nginx/snippets/ 或 /etc/nginx/conf.d/,nginx -T 展开后看会更准。
我当时重点看这几类地方:
- 老站点的 URL 迁移规则。
- 把路径改写成 query string 的规则。
- 网关层为了兼容旧接口写的
$1、$2。 - WordPress、PHP、历史项目留下来的 rewrite 片段。
- Ingress 或面板产品生成的 nginx 配置。
这一步我当时也差点在漏看一次,因为 rewrite 本身太常见了,很多配置看起来只是"正常历史包袱"。
复现步骤:只做隔离环境里的风险确认
如果只是确认自己是否受影响,我建议复现拆成两层。
第一层是配置命中复现:证明你的配置存在危险模式。第二层才是漏洞触发复现:在隔离容器里用公开 PoC 验证旧版本会崩 worker,升级后不再触发。
不要在生产机器上直接跑 PoC。
1. 准备一个旧版本测试环境
用容器最省事,目标是准备一个 1.30.0 或更低版本的 Nginx。示例配置放到测试目录里,不要拿线上配置原封不动去跑。
bash
mkdir -p /tmp/nginx-rift-lab/conf.d
写一个只用于实验的配置,保存到 /tmp/nginx-rift-lab/conf.d/default.conf:
nginx
server {
listen 8080;
server_name _;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log notice;
location / {
rewrite ^/u/([0-9]+)/(.+)$ /profile.php?id=$1&tab=$2 last;
set $rewrite_marker 1;
return 200 "lab\n";
}
}
启动旧版本:
bash
docker run --rm --name nginx-rift-lab \
-p 8080:8080 \
-v /tmp/nginx-rift-lab/conf.d:/etc/nginx/conf.d:ro \
nginx:1.30.0
再开一个终端确认版本:
bash
docker exec nginx-rift-lab nginx -v
docker exec nginx-rift-lab nginx -T | sed -n '/server_name _/,/}/p'
到这里,只能说明配置形态命中了触发条件,还不能说明你已经完成了漏洞利用。
2. 触发验证只看 worker 是否异常重启
公开 PoC 仓库里已经有完整 exploit。我的建议是,只在隔离网络里做 DoS 级别的验证,观察 error log 里是否出现 worker 异常退出、core dumped 或进程重启,不要追求 RCE shell。
日志窗口先开着:
bash
docker logs -f nginx-rift-lab
然后对测试实例发送能命中 rewrite 的请求。真正的触发 payload 需要包含会在 args escape 下膨胀的 URI 字节,公开 PoC 已经覆盖这部分;文章里不贴可直接武器化的请求。
你只需要记录两个结果:
- 旧版本加危险配置:worker 可能异常退出或重启。
- 升级到
1.30.1+或1.31.0+后:同样测试不再触发这个崩溃。
安全验证做到这里就够了。
临时缓解:把未命名捕获换成命名捕获
如果暂时没法马上升级,先把危险 rewrite 改掉。
危险写法:
nginx
rewrite ^/users/([0-9]+)/profile/(.*)$ /profile.php?id=$1&tab=$2 last;
set $rewrite_marker 1;
缓解写法:
nginx
rewrite ^/users/(?<user_id>[0-9]+)/profile/(?<section>.*)$ /profile.php?id=$user_id&tab=$section last;
set $rewrite_marker 1;
这里的重点不是"命名捕获更优雅",而是绕开 $1、$2 这种未命名 capture 的触发条件。
改完一定要测试配置:
bash
sudo nginx -t
sudo systemctl reload nginx
但这个只能算临时处理。只要二进制还在受影响版本里,我还是建议升级。
Debian 上最容易误判的是版本号
我这次修的时候也碰到了这个坑。
Debian 仓库里的 nginx 版本可能还是 1.26.3,看起来不像最新。这里要分清两件事:
- Debian 的稳定仓库经常是旧上游版本加安全 backport,不一定把版本号直接抬到最新。
- nginx 官方这次的明确修复边界是 Open Source
1.30.1+或1.31.0+。
截至我写这篇时,Debian Security Tracker 里 trixie 的 1.26.3-3+deb13u2 仍列为 vulnerable,sid 的 1.30.0-3 已标 fixed。这个状态后面可能变,因为 Debian 可以把补丁 backport 到旧版本号里。
所以判断时别只盯着 nginx -v 的主版本号。要么看 Debian Security Tracker 里对应包状态,要么直接切到 nginx 官方源,用官方已经包含修复的 mainline 包。
我最后选的是第二种:升级到 nginx 官方 mainline 1.31.x。
手动修复流程:以 Debian 切到 nginx 官方 mainline 1.31.x 为例
上面的脚本已经覆盖主流 Linux 发行版。下面这套是 Debian trixie 的手动写法,方便你看清每一步到底动了什么;不是 trixie 的机器,把源里的 trixie 换成你的发行版代号,或者用 lsb_release -cs 动态取。
升级前先备份配置:
bash
sudo cp -a /etc/nginx /etc/nginx.bak.$(date +%F-%H%M)
sudo nginx -t
1. 安装 gpg 和基础依赖
bash
sudo apt update
sudo apt install curl gnupg ca-certificates lsb-release debian-archive-keyring -y
2. 下载 nginx 官方签名 key
bash
curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor \
| sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null
建议顺手核一下 fingerprint。nginx 官方文档给的是 573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62:
bash
gpg --dry-run --quiet --no-keyring --import --import-options import-show \
/usr/share/keyrings/nginx-archive-keyring.gpg
3. 添加 nginx 官方 mainline 源
我这里是 Debian 13 trixie:
bash
echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \
http://nginx.org/packages/mainline/debian/ trixie nginx" \
| sudo tee /etc/apt/sources.list.d/nginx.list
也可以按官方文档用 HTTPS 和系统代号:
bash
echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \
https://nginx.org/packages/mainline/debian $(lsb_release -cs) nginx" \
| sudo tee /etc/apt/sources.list.d/nginx.list
4. 让 apt 优先使用 nginx.org 的包
这一步官方文档也有。加 pinning 之后,apt install nginx 会更明确地选 nginx.org 的包,而不是继续被发行版仓库抢走。
bash
echo -e "Package: *\nPin: origin nginx.org\nPin: release o=nginx\nPin-Priority: 900\n" \
| sudo tee /etc/apt/preferences.d/99nginx
5. 更新 apt 并安装 nginx
bash
sudo apt update
apt-cache policy nginx
sudo apt install nginx
如果之前装的是 Debian 自带拆分包,比如 nginx-common、nginx-core,安装过程中可能会提示配置文件保留或覆盖。我的习惯是先保留现有配置,升级后再手动 diff。
6. 检查版本和配置
bash
nginx -v
sudo nginx -t
sudo systemctl restart nginx
sudo systemctl status nginx --no-pager
修完之后,nginx -v 应该能看到 1.31.x。我这次最后看到的修复结果是这样:

再确认包来源:
bash
apt-cache policy nginx
如果 candidate 或 installed 来源是 nginx.org/packages/mainline/debian,说明已经切到官方 mainline 源。
修完之后还要回头做两件事
第一,重新扫一遍危险 rewrite。
bash
sudo nginx -T > /tmp/nginx-all-after.conf 2>/tmp/nginx-test-after.log
rg -n 'rewrite\s+.*\$\d+.*\?' /tmp/nginx-all-after.conf
升级能修漏洞,但不代表这些历史 rewrite 就值得继续留着。能改成命名捕获就改掉,能删就删。
第二,确认所有 worker 都吃到了新二进制。
bash
ps -eo pid,ppid,cmd | rg 'nginx: (master|worker)'
sudo systemctl restart nginx
nginx -v
只 reload 有时会让你误以为修完了,但老 worker 生命周期没处理干净。安全补丁升级后,我更倾向直接 restart。
最后
这次 CVE-2026-42945 给我的感觉是,Nginx 这种老牌基础设施也不是"稳定到不用看"的东西。
它真正危险的地方不只是"18 年老洞",而是触发点藏在非常日常的 rewrite 配置里。你可能不是特意用了什么高级功能,只是多年前为了兼容旧 URL 写了几行 $1、$2,然后这个入口一直挂在公网。
我的处理建议很简单:
- 先用
nginx -T扫配置,找$1/$2加?的 rewrite。 - 有危险配置就先改成命名捕获。
- Debian 用户不要只看
1.26.3这个版本号,要看安全跟踪状态或包来源。 - 能升级就直接上 nginx 官方修复版本,稳定线至少
1.30.1+,主线就是1.31.x。
这类洞不要等面板、发行版、云厂商全部替你兜底。边缘入口跑在自己机器上,最后还是要自己确认版本、配置和实际进程状态。
参考:
- NGINX 官方安全公告:nginx.org/en/security...
- NGINX 官方发布信息:nginx.org/
- NGINX Linux 包安装文档:nginx.org/en/linux_pa...
- NVD CVE-2026-42945:nvd.nist.gov/vuln/detail...
- DepthFirst NGINX Rift 分析:depthfirst.com/research/ng...
- Debian Security Tracker:security-tracker.debian.org/tracker/CVE...