Skip to content
Attack Surface Management

CVE-2026-42945 (NGINX Rift): Heap Buffer Overflow in the Rewrite Module – Advisory + Nuclei Detection

xer0dayz · · 13 min read

On May 13, 2026, F5 and the research team at depthfirst jointly disclosed CVE-2026-42945, a critical heap buffer overflow in NGINX’s rewrite module. It was given the name NGINX Rift. An unauthenticated attacker triggers it with a single crafted HTTP request, the worker process crashes, and where ASLR is disabled or bypassable, code execution is on the table. The bug was introduced in NGINX 0.6.27 in 2008, which means it sat in the world’s most-deployed web server for roughly 18 years before anyone noticed. Exploitation has been reported in the wild.

This advisory is three things at once. First, a plain-English explanation of what NGINX Rift actually is and which builds are affected. Second, a ready-to-run Nuclei detection template you can copy, paste and scan with today, plus a second template that answers the question version detection cannot: whether your configuration actually reaches the vulnerable code. Third, a walkthrough of how to find every unpatched NGINX instance across an attack surface with Sn1per, including the ones nobody remembers deploying.

There is one thing we want to put up front, because it is the single most consequential detail in this advisory and it is missing from most coverage: upgrading to the version that fixes NGINX Rift leaves you exposed to a worse bug. More on that below.

CVE-2026-42945 at a glance

  • CVE: CVE-2026-42945, “NGINX Rift” – heap-based buffer overflow (CWE-122) in ngx_http_rewrite_module.
  • Impact: unauthenticated worker-process crash from a single HTTP request. Denial of service through crash or restart loop; remote code execution where ASLR is disabled or bypassable.
  • CVSS: 9.2 Critical under CVSS 4.0, 8.1 High under CVSS 3.1.
  • Affected: every build from 0.6.27 through 1.30.0, plus NGINX Plus R32 through R36, plus every downstream that ships the same ngx_http_script.c.
  • Fixed: 1.30.1 (stable) and 1.31.0 (mainline). NGINX Plus R37, R36 P4 or R32 P6.
  • Status: public proof-of-concept exists and needs only standard Python sockets. Exploitation reported in the wild.
  • Age: introduced 2008, disclosed 2026. Eighteen years of exposure.
  • Config-gated: yes, and nginx -t will not warn you. See below.
  • Fix now: do not stop at 1.30.1. Go to 1.30.4 or 1.31.3.

What is NGINX Rift?

NGINX evaluates complex string expressions – the things you build with variables, captures and literals – in two passes. The first pass, LEN, walks the expression and measures how many bytes the result will need, so NGINX can allocate a buffer of exactly that size. The second pass, VALUE, walks the same expression again and writes the bytes into that buffer. The correctness of the whole scheme rests on one assumption: that both passes see the same data and therefore agree on the size.

CVE-2026-42945 breaks that assumption inside src/http/ngx_http_script.c. An is_args state flag, set during the length-calculation pass, leaks into the copy pass. When a rewrite replacement combines an unnamed PCRE capture with a question mark, ngx_escape_uri ends up writing more bytes than the LEN pass budgeted for. The result is a classic heap buffer overflow: the copy runs off the end of an allocation that was correctly sized for a different answer.

The reason this matters more than a typical overflow is the position NGINX occupies. It is not an application dependency buried three layers deep; it is the process that terminates connections at the edge, in front of more than a third of the web. A bug reachable from a single unauthenticated GET, in a module as ordinary as rewrite, is about as broadly exposed as software gets. Researchers demonstrated the crash against a current Ubuntu build with modern mitigations enabled, using nothing but standard Python sockets, 349 padding bytes and 2,000 URI-escapable characters to produce a deterministic 4,000-byte overflow.

Worth noting for triage: this is the same underlying defect class as CVE-2026-42533, the map directive overflow disclosed two months later. Same file, same two-pass contract violation, different directive as the entry point. If you are patching for one, read the section on version windows below before you pick a target release, because the two were fixed in different builds and the obvious upgrade closes only one of them.

Affected and fixed versions

The version picture for NGINX Rift on its own is refreshingly simple. One contiguous window, one upper bound.

Branch Affected Fixed
Stable 1.30.x 1.30.0 1.30.1
Mainline 1.31.x none (branched after the fix) 1.31.0 and later
Everything older 0.6.27 through 1.29.x Upgrade
Legacy 0.6.27 – 0.9.7 All No patch planned. Migrate.
NGINX Plus R32 through R36 R37, R36 P4, or R32 P6

Here is the trap, and it is an expensive one. The obvious response to this advisory is to upgrade to 1.30.1, because 1.30.1 is what the NGINX Rift advisory tells you to install. That is correct for this CVE and wrong for your server. On July 15, 2026, F5 shipped three more memory-corruption fixes: CVE-2026-42533 (the map directive overflow, with a weaponized public exploit), CVE-2026-60005 (uninitialised memory disclosure in the slice module) and CVE-2026-56434 (use-after-free in the SSI module). Those landed in 1.30.4 and 1.31.3. An operator who read the May advisory, upgraded to 1.30.1, and considered the matter closed is sitting three releases behind on a bug with public ASLR-defeating exploit code.

So the correct target is not 1.30.1. It is 1.30.4 on stable or 1.31.3 on mainline, which closes all four in one change. If you take one action from this page, make it that.

Why version alone cannot clear a host

NGINX Rift is config-gated. An unpatched build is only actually exploitable when the configuration contains a specific directive pattern, and an attacker cannot create that pattern remotely. The vulnerable shape needs three ingredients together:

  • a rewrite directive, followed by a rewrite, if or set directive,
  • an unnamed PCRE capture group such as $1 or $2,
  • and a question mark in the replacement string.

That last combination is not exotic. Any rewrite that maps a path segment into a query string produces it – rewrite ^/old/(.*)$ /new?path=$1 last; is the canonical example, and variants of it ship in vendor and framework configuration templates all over the internet.

The operationally important part: nginx -t does not flag this pattern. The configuration is syntactically valid, so the standard config test passes clean and nothing warns the operator. There is no deprecation notice, no lint failure, no startup message. If you have been relying on nginx -t as your configuration safety net, it has nothing to say about this bug.

The practical consequence for scanning is that a version match is a patch-gap finding, not a proven exploitable RCE. Two further things break the inference from banner to exploitability, and both are common. Distributions backport security fixes without bumping the upstream version string, so a banner inside the vulnerable range may already be patched. And the banner you read at the edge may belong to a CDN or reverse proxy rather than the origin you think you are testing. We build for both facts below: a remote template that finds the gap, and a local template that decides whether the gap is reachable.

Am I affected? Quick manual checks

The authoritative check is local and takes one command:

# On the server:
nginx -v

# nginx version: nginx/1.29.2   -> affected
# nginx version: nginx/1.30.1   -> fixed for Rift, still exposed to CVE-2026-42533
# nginx version: nginx/1.30.4   -> fixed for all four 2026 memory-corruption CVEs

Remotely and unauthenticated, the Server response header is the usual signal, with the footer of NGINX’s own default error pages as a fallback when the header has been trimmed:

curl -sI https://target.example.com | grep -i '^server:'

# Fallback - NGINX prints its version in its own 404 page footer:
curl -s https://target.example.com/does-not-exist-12345 | grep -i 'center>nginx'

Then check whether the configuration actually reaches the bug:

# Does any rewrite combine a capture with a question mark?
grep -rnE 'rewrite\s+\S+\s+[^;]*(\$[1-9][^;]*\?|\?[^;]*\$[1-9])' /etc/nginx/

Be clear-eyed about what these can and cannot tell you. Absence of a version banner is not evidence of patching. server_tokens off removes the version entirely, and plenty of deployments strip or rewrite the Server header at the edge. A host that discloses nothing is unknown, not clean. And a clean grep today is not a permanent clearance, because any config change or vendor template can introduce the pattern tomorrow. Patch the binary regardless of what the configuration audit says.

CVE-2026-42945 Nuclei detection template

Below is the full template. It is non-destructive: it sends one ordinary GET, reads the version NGINX advertises about itself, and compares it against the fixed builds. It sends no crafted URI, no padding, and never reaches the vulnerable code path. It is safe to run against production.

We want to be direct about why it is a version check rather than an active probe. Confirming this bug actively means overflowing a worker heap. That crashes the worker, which is the denial of service the CVE describes. There is no safe active confirmation, so anyone shipping one is shipping an exploit. Do not run one against an asset you do not own, and do not run one against a bug bounty target at all.

id: CVE-2026-42945

info:
  name: NGINX Rift - rewrite Module Heap Buffer Overflow (CVE-2026-42945)
  author: xer0dayz
  severity: critical
  description: |
    Heap-based buffer overflow (CWE-122) in ngx_http_rewrite_module. An is_args
    state flag set during the length-calculation pass leaks into the copy pass,
    so ngx_escape_uri writes past the allocated buffer when a rewrite replacement
    combines an unnamed PCRE capture with a question mark.

    NON-DESTRUCTIVE and PASSIVE. One ordinary GET, version comparison only. Sends
    no crafted URI and never reaches the vulnerable code path.

    Treat a hit as a patch-gap finding, NOT a proven exploitable RCE. Exploitation
    is config-gated and nginx -t does NOT flag the vulnerable pattern.
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
    cvss-score: 8.1
    cve-id: CVE-2026-42945
    cwe-id: CWE-122
  metadata:
    verified: false
    max-request: 2
    vendor: f5
    product: nginx
    patched-versions: "1.30.1, 1.31.0"
    exploited-in-the-wild: true
  tags: cve,cve2026,nginx,f5,nginx-rift,overflow,rce,patch-gap,passive

http:
  - method: GET
    path:
      - "{{BaseURL}}"
      - "{{BaseURL}}/{{randstr}}"

    stop-at-first-match: true
    redirects: false

    # One contiguous vulnerable window. Stable is fixed from 1.30.1 and mainline
    # from 1.31.0, so every build above 1.30.0 is patched and a single upper bound
    # is correct here - unlike CVE-2026-42533, which needs two windows.
    matchers:
      - type: dsl
        name: nginx-0.6.27-to-1.30.0
        dsl:
          - "version != '' && compare_versions(version, '>= 0.6.27', '< 1.30.1')"

    extractors:
      # Server header first, then the version NGINX prints in the footer of its
      # own default error pages. Anchoring the body form to <center> avoids
      # matching proxied application content that merely mentions an nginx version.
      - type: regex
        name: version
        part: response
        group: 1
        internal: true
        regex:
          - '(?:[Ss]erver:\s*nginx/|<center>nginx/)([0-9]+\.[0-9]+\.[0-9]+)'

      - type: regex
        name: nginx_version
        part: response
        group: 1
        regex:
          - '(?:[Ss]erver:\s*nginx/|<center>nginx/)([0-9]+\.[0-9]+\.[0-9]+)'

Save it and run it:

nuclei -t CVE-2026-42945.yaml -l targets.txt

We verified the version window against a twelve-version mock matrix covering every boundary, including 1.30.0 (last affected), 1.30.1 (first fixed) and the 1.31.x mainline. All twelve boundary cases pass.

The second template: auditing the configuration

Version detection finds the patch gap. It cannot tell you whether the gap is reachable, and as established above, that depends entirely on the configuration. So we ship a second, file-protocol template that reads the config tree and looks for the vulnerable directive pattern directly.

Here is the matcher that covers NGINX Rift. The full file also carries a matcher for the map directive pattern behind CVE-2026-42533, which we walk through in that advisory:

file:
  - extensions:
      - all

    matchers:
      # The \S+ consumes the match pattern so that a '?' used as a PCRE quantifier
      # in the pattern itself is not mistaken for a '?' in the replacement. Both
      # orderings are covered: capture-then-question-mark (/new/$1?x=1) and
      # question-mark-then-capture (/new?path=$1).
      - type: regex
        name: cve-2026-42945-rewrite-capture-with-querystring
        regex:
          - '(?im)^\s*rewrite\s+\S+\s+[^;\n]*(?:\$[1-9][^;\n]*\?|\?[^;\n]*\$[1-9])'

Two things to know before you run it. The -file flag is required – Nuclei disables file-protocol templates by default and will otherwise exit with “no templates provided for scan”, which looks exactly like a clean result if you are not reading closely. And point it at the whole config tree rather than nginx.conf alone, because on Debian and Ubuntu the rewrite rules usually live in extensionless files under sites-enabled/:

nuclei -file -t nginx-script-engine-config-audit.yaml -target /etc/nginx/

The \S+ in that regex is doing real work. Without it, a perfectly safe rule like rewrite ^/docs(/v2)?/(.*)$ /help/$2 last; produces a false positive, because the ? is a PCRE quantifier in the match pattern rather than a literal in the replacement. Consuming the pattern token first removes that entire class of noise.

Treat a hit as “review this by hand.” NGINX evaluation order is a runtime property spanning includes, server blocks and location blocks, and no regex can prove reachability. Equally, treat a clean result as provisional: split configurations can put the ingredients in separate files, and a clean audit is never a reason to leave an unpatched binary running.

Finding unpatched NGINX across your attack surface with Sn1per

One template against one host answers one question. The harder problem is the NGINX instance nobody remembers deploying: the staging box behind a forgotten DNS record, the appliance that embeds NGINX as a component, the ingress controller in a cluster somebody stood up for a demo in 2023. Those are the hosts that stay on 1.29.x indefinitely, because nothing in the patch process knows they exist.

That is an attack surface discovery problem, not a scanning problem, and it is what Sn1per is built for. The workflow is discovery first, detection second:

# Discover the surface, then sweep it
sniper -t example.com -m recon
sniper -t example.com -m web

Both templates from this advisory ship in Sn1per’s curated set, so hosts running affected NGINX are flagged automatically as the surface is enumerated. Because the check is passive, it runs safely against production during normal scheduled scans – no worker crashes, no restart loops, no incident.

The practical shape of the result is a list of every NGINX version across the estate rather than a single yes-or-no. That is the artifact that actually drives remediation, because it tells you how many hosts are on 1.30.1 and think they are done.

Which Sn1per edition fits

  • Sn1per Community Edition – free and open source. Run the templates by hand across targets you already know about. Good for validating a handful of hosts.
  • Sn1per Professional 2026 – adds the web UI, scheduled scans and reporting. This is the tier that turns a one-off check into continuous coverage, which is what you want for a CVE class where the vulnerable population keeps being rediscovered.
  • Sn1per Enterprise – multi-workspace attack surface management with continuous monitoring and alerting. Built for estates where “how many NGINX instances do we run” is a genuinely hard question.

Remediation and mitigation

  1. Upgrade, and pick the right target. Not 1.30.1. Go to 1.30.4 (stable) or 1.31.3 (mainline), which closes CVE-2026-42945, CVE-2026-42533, CVE-2026-60005 and CVE-2026-56434 together. NGINX Plus users want R37 or the relevant patch level.
  2. Patch the downstream products too. NGINX Ingress Controller, NGINX Gateway Fabric, NGINX App Protect WAF and NGINX Instance Manager all ship the same script engine and need their own patched builds.
  3. Interim mitigation only, if you genuinely cannot patch: replace unnamed regex captures with named captures in rewrite replacements, or remove the question mark from any replacement that also references a capture. This is mitigation, not remediation.
  4. Legacy builds have no path. Versions 0.6.27 through 0.9.7 will not receive a patch. Migration to a supported branch is the only option.
  5. Re-audit after config changes. Vendor and framework configuration templates routinely ship rewrite rules with query-string replacements, so the pattern can be reintroduced by a change that has nothing to do with security.

Frequently asked questions

Is CVE-2026-42945 remote code execution or just denial of service?
Both, depending on the target. The reliable, immediate impact is a worker-process crash or restart loop from a single unauthenticated request. Code execution is reachable where ASLR is disabled or can be bypassed. F5’s advisory frames the impact conservatively as denial of service with possible code execution; treat it as RCE-capable for risk purposes, because public exploit code exists and exploitation has been reported in the wild.

My server runs nginx 1.30.1. Am I safe?
Safe from NGINX Rift, not safe overall. 1.30.1 fixes CVE-2026-42945 but predates the July 15 release that fixed CVE-2026-42533, CVE-2026-60005 and CVE-2026-56434. CVE-2026-42533 has weaponized public exploit code that defeats ASLR. Upgrade to 1.30.4.

Will nginx -t tell me if my config is vulnerable?
No. The vulnerable pattern is syntactically valid, so nginx -t passes clean and emits no warning. This is why the configuration audit template exists.

Can I confirm the vulnerability by sending a test request?
Not safely. Confirming it means overflowing a worker heap, which crashes the worker – that is the denial of service the CVE describes. Use version detection plus a configuration audit instead. Never attempt active confirmation against a bug bounty target or any asset you do not own.

My scanner found nothing. Does that mean I am patched?
No. server_tokens off removes the version banner entirely, and many deployments strip or rewrite the Server header at the CDN or load balancer. A host that discloses no version is unknown, not clean. Confirm locally with nginx -v.

Written by

xer0dayz

Founder of XeroSecurity.

Try it free

See your attack surface like a pentester would.

Sn1per finds, ranks, and exploits real vulnerabilities autonomously — the same way attackers do.