Skip to main content
2026-05-14
11 min read

NGINX Rift (CVE-2026-42945): The 18-Year-Old Rewrite Bug That Hands an Attacker Your Worker Process

NGINX Rift (CVE-2026-42945): The 18-Year-Old Rewrite Bug That Hands an Attacker Your Worker Process

On May 13, 2026, F5 published K000161019 and the security advisories list at nginx.org picked up a new entry. The bug, branded "NGINX Rift" by its discoverer and tracked as CVE-2026-42945, is a heap buffer overflow in the rewrite module that has been sitting in ngx_http_script.c since the 0.6.27 release in 2008. Every nginx release between then and 1.30.0 is vulnerable. So is NGINX Plus through R36. So is every F5 product that ships nginx internally, including their commercial Ingress Controller, App Protect WAF, and Instance Manager.

A working remote code execution PoC is public on GitHub. There is no in-the-wild exploitation reported as of this morning, but the PoC repository is small enough to read in one sitting and the exploit primitive is deterministic. The clock is short.

This post covers what the bug actually is, the one-line grep that tells you whether your config is exploitable (because the F5 advisory's "vulnerable" framing is broader than your actual exposure), the patch matrix across distros, and the long tail of OpenResty, Kong, APISIX, and other downstream products that have no advisory yet but ship the same vulnerable code.

TL;DR

  • CVE-2026-42945, CVSS v4 9.2 / v3 8.1. Heap buffer overflow in ngx_http_rewrite_module reachable by an unauthenticated HTTP request against any nginx running a vulnerable rewrite pattern.
  • Affected: NGINX Open Source 0.6.27 through 1.30.0; NGINX Plus R32 through R36; F5 NGINX Ingress Controller 3.5.0-5.4.1, NGINX App Protect WAF 4.x and 5.x, NGINX Gateway Fabric, NGINX Instance Manager. OpenResty, Tengine, Angie, FreeNGINX, Kong, APISIX and the Kubernetes ingress-nginx project all ship the same ngx_http_script.c and should be treated as vulnerable until their maintainers ship a patched release.
  • Fixed in: nginx 1.31.0 (mainline) and 1.30.1 (stable). NGINX Plus R36 P4, R35 P2, R32 P6.
  • The trigger is operator-written config, not attacker-controlled config. A rewrite directive whose replacement contains ? and uses an unnamed capture ($1, $2, etc.) referenced again by set, if, or a subsequent rewrite is enough. The attacker just sends one HTTP request with the right URL.
  • Successful exploitation lands code execution as the nginx worker user (often www-data or nginx). Workers hold the TLS private key in memory and serve responses, so even non-root worker access is a serious incident.
  • Detection: there are no published WAF rules from Cloudflare, AWS, or OWASP CRS yet. Grep your own configs. The one-liner is below.

Prerequisites

  • Shell access to any host that runs nginx, or to a Kubernetes cluster running an nginx-based ingress controller.
  • The nginx config tree (/etc/nginx/, or your container image's equivalent).
  • Patience for one round of grep followed by either a package upgrade or a config audit.

What the bug actually is

The rewrite module compiles every rewrite, set, if, and return directive into a small bytecode that runs once per request. The compiler produces two code arrays: a "length" array that calculates how many bytes the rewritten string will occupy, and a "value" array that actually writes those bytes.

Two state bits flow through this machine. One is is_args, which records whether the rewrite has crossed into the query-string portion of the URL. The other is the destination buffer pointer. The bug is that those two bits get out of sync when the rewrite uses an unnamed PCRE capture and the replacement contains ?.

Concretely, after a rewrite ^/api/(.+) /v2/$1?internal=1 break; runs once, the engine permanently flips is_args=1 on the main script engine. The length pass for the next rewrite (or set, or if referencing $1) runs through a zeroed sub-engine where is_args=0, so the capture-length code returns the raw byte count of $1. The copy pass sees is_args=1 on the main engine and routes the same bytes through ngx_escape_uri, which expands characters like +, %, &, and space into their percent-encoded forms. The destination buffer was sized for the raw count, so the expanded bytes write past the end of the allocation.

The corruption lands in the request pool. With cross-request heap shaping, the PoC walks the overflow into the ngx_pool_cleanup_t handler pointer and gets system() called with attacker-controlled arguments. Worker code execution follows. All the technical detail is in the depthfirst writeup and the fix commit.

Two important details for operators:

  1. This is not a config-poisoning bug. Some vulnerability writeups make a bug sound less serious by noting it requires attacker-controlled nginx.conf. This one does not. Vulnerable configs are operator-written, common in API-gateway and reverse-proxy deployments, and the attacker only needs to send an HTTP request.
  2. nginx -t does not flag the pattern. The vulnerable config is syntactically valid. There is no warning from the standard config check. You have to grep.

Find vulnerable configs in your tree

The dangerous pattern needs three things together: a rewrite whose replacement contains ?, the replacement contains an unnamed capture ($1 through $9), and the same capture is read by a later rewrite, set, or if in the same server or location block.

The fast heuristic is a regex against your full config tree:

# List rewrite directives whose replacement carries both '?' and a $N capture.
grep -RHnE 'rewrite[[:space:]]+[^;]+\$[1-9][^;]*\?|rewrite[[:space:]]+[^;]+\?[^;]*\$[1-9]' /etc/nginx/ 2>/dev/null

That gives you the candidate rewrite lines. From there, walk the server and location blocks each lives in and confirm whether any set $foo $1, if ($1 = "..."), or a second rewrite references the same capture. Those are the exploitable combinations.

If you run nginx inside Kubernetes via ingress-nginx, the same grep against the generated config inside the controller pod is the answer:

kubectl -n ingress-nginx exec -ti deploy/ingress-nginx-controller -- \
  sh -c "cat /etc/nginx/nginx.conf | grep -nE 'rewrite[[:space:]]+[^;]+\\\$[1-9][^;]*\\?'"

The generated config aggregates every Ingress's annotations into one file. Snippets, rewrite-target, and configuration-snippet are the common sources.

If the grep is empty across your entire fleet, you are not currently exploitable. Upgrade anyway, because the next config change a developer pushes may add a vulnerable pattern, and you would rather not be running a binary whose CVE you already shrugged off.

The patch matrix

+--------------------------------+-------------------------------------+
| Software                       | Patched version                     |
+--------------------------------+-------------------------------------+
| nginx (mainline)               | 1.31.0                              |
| nginx (stable)                 | 1.30.1                              |
| NGINX Plus R36                 | R36 P4                              |
| NGINX Plus R35                 | R35 P2                              |
| NGINX Plus R32                 | R32 P6                              |
| F5 NGINX Ingress Controller    | 5.4.2 (when released)               |
| F5 NGINX App Protect WAF       | 4.16.1 / 5.8.1 (when released)      |
| F5 NGINX Gateway Fabric        | 2.5.2 (when released)               |
| F5 NGINX Instance Manager      | 2.21.2 (when released)              |
| OpenResty                      | No advisory yet                     |
| Tengine, Angie, FreeNGINX      | No advisory yet                     |
| Kong, APISIX                   | No advisory yet                     |
| Kubernetes ingress-nginx       | Retired, no patch coming            |
+--------------------------------+-------------------------------------+

Distro status as of this morning (2026-05-14):

  • Debian (tracker): bullseye, bookworm, trixie, forky all show vulnerable. Only sid has the fixed 1.30.0-3 package landed.
  • AlmaLinux: backport for 8, 9, and 10 published in the testing repos using the upstream patch. Worth pulling if you cannot wait for RHEL.
  • RHEL, Ubuntu, Alpine: no published advisories yet.

The Kubernetes ingress-nginx line is the one to flag for your platform team. That project went EOL in March 2026 and there is no maintainer left to ship a patched container image. If you are still on it, this is the second CVE in nine days where the answer is "there is no patch coming, plan the Gateway API migration." We covered that migration in a separate post.

If you cannot patch in the next 24 hours

There is no published WAF rule from Cloudflare's managed set, AWS WAF managed rules, or the OWASP Core Rule Set as of this morning. A custom rule that drops URLs containing combinations of percent-encoded bytes and unencoded special characters can blunt the most obvious PoC, but the underlying primitive is broad and the attacker can vary their input shape considerably.

The realistic short-term mitigations:

  1. Audit and edit the vulnerable rewrite blocks. If a rewrite line carries the dangerous pattern and you can rewrite it without the ? or the unnamed capture, do that. Most API-gateway rewrites can move the query-string concatenation into a set $args ... statement instead of stuffing it into the rewrite replacement.
  2. Front nginx with a non-nginx proxy that can drop malformed paths. A separately-deployed Envoy or HAProxy in front of nginx does not magically rescue you, because both proxies forward the URL path unchanged by default. But you can add path-normalisation or path-length limits at the front proxy that make exploitation harder. This buys time, not safety.
  3. Run workers under a tightly scoped systemd unit. NoNewPrivileges=yes, ProtectSystem=strict, ProtectHome=yes, PrivateTmp=yes, RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6, SystemCallFilter=@system-service, and especially MemoryDenyWriteExecute=yes. None of these stop the corruption, but MemoryDenyWriteExecute=yes plus the RestrictAddressFamilies line break the PoC's preferred follow-up of dropping a shell. You still want to upgrade.

Removing the rewrite module entirely at build time is possible (./configure --without-http_rewrite_module) but breaks more than it fixes for most deployments.

What to watch in your logs

There is no canonical detection signature yet. The PoC needs to send a stream of requests to shape the heap before the trigger request arrives, so a spike in same-prefix requests from one source to URLs that hit your rewrite blocks is the kind of pattern that should raise eyebrows. Two queries worth running over the last week's access logs:

# Many requests to the same URL prefix from the same source in a short window
awk '{print $1, $7}' /var/log/nginx/access.log \
  | sort | uniq -c | sort -rn | head -50
# Requests where the path contains long sequences of percent-encoded bytes
grep -E '%[0-9A-Fa-f]{2}.*%[0-9A-Fa-f]{2}.*%[0-9A-Fa-f]{2}.*%[0-9A-Fa-f]{2}' \
  /var/log/nginx/access.log | head -50

Neither is specific enough to alert on, but both are good enough for retrospective investigation if you suspect compromise. Combine with worker process crash reports in your system journal (journalctl -u nginx).

The AI-found angle

The bug was found by an autonomous code-audit system run by depthfirst, in a six-hour run on the nginx codebase in April 2026. The same run surfaced four other memory-corruption issues, all of which F5 confirmed. The disclosure timeline was tight (April 18 found, April 21 reported, April 28 working RCE PoC, May 13 advisory), which is roughly the speed an AI-assisted research workflow lets a small team operate at.

The framing matters less than the implication. The codebase that nobody has shipped a critical RCE against in 18 years now has one shipped against it inside a single afternoon of automated review. The cadence of these "old codebase, new CVE" disclosures is going to keep getting faster, and the operational discipline that lets you patch in 24 hours instead of three weeks is going to keep getting more valuable.

Sources

Grep first. Patch second. Plan the ingress-nginx migration if you have not already.

Published: 2026-05-14|Last updated: 2026-05-14T12:30:00Z

Found an issue?

Also worth your time on this topic