欢迎光临
我们一直在努力

Nginx CVE‑2026‑42945:隐藏18年高危漏洞被曝光(附解决方案)

Nginx CVE‑2026‑42945:隐藏18年高危漏洞被曝光(附解决方案)头图

自查

  • 影响范围: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 去怼生产。

    先看版本:

    nginx -v
    nginx -V 2>&1 | head

    如果是 Open Source 1.30.1+ 或 1.31.0+,这条 CVE 已经在官方修复边界内。否则继续看配置。

    先粗扫所有 rewrite:

    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。示例配置放到测试目录里,不要拿线上配置原封不动去跑。

    mkdir -p /tmp/nginx-rift-lab/conf.d

    写一个只用于实验的配置,保存到 /tmp/nginx-rift-lab/conf.d/default.conf:

    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";
    }
    }

    启动旧版本:

    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

    再开一个终端确认版本:

    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。

    日志窗口先开着:

    docker logs -f nginx-rift-lab

    然后对测试实例发送能命中 rewrite 的请求。真正的触发 payload 需要包含会在 args escape 下膨胀的 URI 字节,公开 PoC 已经覆盖这部分;文章里不贴可直接武器化的请求。

    你只需要记录两个结果:

    • 旧版本加危险配置:worker 可能异常退出或重启。
    • 升级到 1.30.1+ 或 1.31.0+ 后:同样测试不再触发这个崩溃。

    安全验证做到这里就够了。

    临时缓解:把未命名捕获换成命名捕获

    如果暂时没法马上升级,先把危险 rewrite 改掉。

    危险写法:

    rewrite ^/users/([0-9]+)/profile/(.*)$ /profile.php?id=$1&tab=$2 last;
    set $rewrite_marker 1;

    缓解写法:

    rewrite ^/users/(?<user_id>[0-9]+)/profile/(?<section>.*)$ /profile.php?id=$user_id&tab=$section last;
    set $rewrite_marker 1;

    这里的重点不是“命名捕获更优雅”,而是绕开 $1、$2 这种未命名 capture 的触发条件。

    改完一定要测试配置:

    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 动态取。

    升级前先备份配置:

    sudo cp -a /etc/nginx /etc/nginx.bak.$(date +%F-%H%M)
    sudo nginx -t

    1. 安装 gpg 和基础依赖

    sudo apt update
    sudo apt install curl gnupg ca-certificates lsb-release debian-archive-keyring -y

    2. 下载 nginx 官方签名 key

    curl https://nginx.org/keys/nginx_signing.key | gpg –dearmor \\
    | sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null

    建议顺手核一下 fingerprint。nginx 官方文档给的是 573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62:

    gpg –dry-run –quiet –no-keyring –import –import-options import-show \\
    /usr/share/keyrings/nginx-archive-keyring.gpg

    3. 添加 nginx 官方 mainline 源

    我这里是 Debian 13 trixie:

    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 和系统代号:

    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 的包,而不是继续被发行版仓库抢走。

    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

    sudo apt update
    apt-cache policy nginx
    sudo apt install nginx

    如果之前装的是 Debian 自带拆分包,比如 nginx-common、nginx-core,安装过程中可能会提示配置文件保留或覆盖。我的习惯是先保留现有配置,升级后再手动 diff。

    6. 检查版本和配置

    nginx -v
    sudo nginx -t
    sudo systemctl restart nginx
    sudo systemctl status nginx –no-pager

    修完之后,nginx -v 应该能看到 1.31.x。我这次最后看到的修复结果是这样:

    Nginx CVE-2026-42945 修复后版本截图

    再确认包来源:

    apt-cache policy nginx

    如果 candidate 或 installed 来源是 nginx.org/packages/mainline/debian,说明已经切到官方 mainline 源。

    修完之后还要回头做两件事

    第一,重新扫一遍危险 rewrite。

    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 都吃到了新二进制。

    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 官方安全公告: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
    赞(0)
    未经允许不得转载:171主机测评 » Nginx CVE‑2026‑42945:隐藏18年高危漏洞被曝光(附解决方案)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址