一、自查
影响范围: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 官方二进制源支持列表里的系统,脚本会停下来让你手动确认。
自查脚本
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 通过后再重启。
一键修复脚本
#!/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+。
触发点不是所有 rewrite,而是这个组合
这次漏洞卡在一个很具体的配置模式上。
按 NVD 的描述,满足下面几件事才危险:
使用 ngx_http_rewrite_module 里的 rewrite 指令。
正则里用了未命名 PCRE 捕获,比如 $1、$2。
替换字符串里带了 ?。
这个 rewrite 后面又跟着 rewrite、if 或 set 指令。
大概长这样:
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。
公开 PoC 已经有了,甚至有 RCE 复现仓库。这里我不贴直接拿去打 shell 的命令,没必要。对大多数维护自己服务器的人来说,第一步应该是确认配置是否命中触发模式,而不是拿 payload 去怼生产。
最后
这次 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 官方安全公告:https://nginx.org/en/security_advisories.html
NGINX 官方发布信息:https://nginx.org/
NGINX Linux 包安装文档:https://nginx.org/en/linux_packages.html
NVD CVE-2026-42945:https://nvd.nist.gov/vuln/detail/CVE-2026-42945
DepthFirst NGINX Rift 分析:https://depthfirst.com/research/nginx-rift-achieving-nginx-rce-via-an-18-year-old-vulnerability
Debian Security Tracker:https://security-tracker.debian.org/tracker/CVE-2026-42945