Blog

NGINX Rift: An 18-Year-Old Bug in the Rewrite Module and Why Your Server Is at Risk Right Now.

NGINX_Rift
Uncategorized

NGINX Rift: An 18-Year-Old Bug in the Rewrite Module and Why Your Server Is at Risk Right Now.

Picture this: your nginx has been running for years. HTTPS is configured, security headers are set, fail2ban is blocking bots. You’ve closed all the obvious holes. Then someone sends a single HTTP request — just like thousands of others — and your worker process crashes. If ASLR is disabled on your system, they’re already executing arbitrary code as the nginx user.

Nothing looks broken from the outside. Nginx spawned a new worker and kept going. No alert fired. Just a line that flashed through the logs: “worker process exited on signal 11.”

Welcome to CVE-2026-42945, known as NGINX Rift.

IT WAS EVERYWHERE — AND NOBODY NOTICED

On May 13, 2026, F5 and security research platform depthfirst jointly disclosed a critical vulnerability in ngx_http_rewrite_module — the module responsible for processing rewrite, if, and set directives, which handle URL rewriting. Nearly every non-trivial nginx configuration uses it.

The vulnerability has been present since 2008. Since nginx 0.6.27. It sat in the source code through every version, every security audit, every update. 18 years.

Affected: NGINX Open Source 0.6.27 — 1.30.0, NGINX Plus R32 — R36, and everything built from the same ngx_http_script.c — including Ingress NGINX in Kubernetes clusters.

CVSS v4.0: 9.2 Critical. CVSS v3.1: 8.1 High.

One notable detail about how it was found: depthfirst loaded the nginx source code into their AI-powered code analysis system and got results in roughly six hours. Six hours versus eighteen years. That’s not an indictment of nginx — it’s a signal that manual code review no longer keeps up with this class of vulnerability.

WHAT IS A HEAP BUFFER OVERFLOW AND WHY DOES IT MATTER

Before diving into the specific bug — a moment of context, because this term is important.

When nginx processes a request, it allocates a chunk of memory on the heap to work with the data. A buffer overflow happens when a program writes more data than fits into that allocated chunk and starts overwriting adjacent memory. That adjacent memory can contain function pointers, control structures, anything.

Why this is critical: if an attacker can control what gets written past the buffer boundary, they can overwrite a function pointer with their own address. The next time nginx calls that function, the attacker’s code runs instead. That is Remote Code Execution.

HOW THE BUG WORKS

nginx processes rewrite rules in two passes through the code.

The first pass is the scout. It walks through the rule and calculates: how many bytes need to be allocated for the resulting string? It examines the pattern, the replacement string, the captures — and builds the required buffer size.

The second pass is the executor. It takes the already-allocated buffer and copies the data into it.

The problem lives in a single flag called is_args. It gets set during the first pass when a question mark appears in the replacement string — that’s the signal that a query string follows, parameters like ?id=123. The logic is correct: the flag exists so the first pass can accurately calculate the length.

But the flag never gets cleared between passes. It leaks into the second pass — where it causes the ngx_escape_uri function to operate in a different mode, with different escaping logic and a different length calculation. The result: the second pass writes more bytes than the first pass allocated. The buffer overflows.

The vulnerability only triggers when two conditions are met simultaneously:
— unnamed PCRE captures in the rule: $1, $2, $3 (a regex group without a name — just plain parentheses)
— a question mark in the replacement string

A typical example — a standard API proxy:

location /api/ {
    rewrite ^/api/(.*)$ /internal?id=$1 last;
}

This has both an unnamed capture (.*) — that’s $1 — and a question mark before id=$1. Both conditions are met. This config is vulnerable.

And this isn’t exotic. It’s a standard pattern found in half the nginx tutorials out there. The people who wrote those configs weren’t doing anything wrong — they just didn’t know a flag inside the engine wasn’t being reset properly.

HOW EXPLOITATION WORKS

The attacker sends an HTTP request to the vulnerable endpoint. The URI contains a long string of repeating characters — hundreds of plus signs, for example.

The first pass calculates the buffer size using normal logic, without accounting for the leaked flag. The buffer gets allocated.

The second pass starts copying — and because of the incorrect flag, ngx_escape_uri begins adding escape sequences (%2B instead of +, for example). The string grows. It crosses the buffer boundary. It overwrites adjacent memory.

The critical detail: the data being written past the boundary comes directly from the attacker’s URI. They control exactly what ends up there. This is not random memory corruption — it’s a controlled write.

WHAT HAPPENS NEXT DEPENDS ON YOUR SYSTEM CONFIGURATION

With ASLR enabled (Address Space Layout Randomization, kernel.randomize_va_space = 2):
Every time a process starts, memory addresses are randomized. The attacker doesn’t know where in memory the thing they want to overwrite actually lives. The overflow happens, but it lands in a random location — nginx crashes with signal 11 (segfault) and restarts. This is Denial of Service: annoying, the site hiccups, but not catastrophic.

Without ASLR, or with ASLR disabled:
Addresses are predictable. The attacker knows exactly where to write. The result is deterministic RCE: one request, arbitrary code running as the nginx user.

There’s a catch with ASLR though: depthfirst documented a technique in their PoC called progressive heap shaping. This is a series of carefully crafted requests that gradually arrange the heap into a known layout — so that the target structures end up at predictable addresses even with ASLR enabled. It’s more complex than a single request and takes time, but it works. ASLR is not an absolute defence — it raises the bar, but it doesn’t close the hole.

On May 16 — three days after the PoC was published — VulnCheck detected the first real exploitation attempts in the wild.

THE ATTACK CHAIN IN PRACTICE

NGINX Rift is most dangerous not on its own, but chained with other May 2026 vulnerabilities.

NGINX Rift gives an attacker code execution inside an nginx worker process — running as www-data or nginx, unprivileged. Unpleasant on its own, but not root.

Dirty Frag (CVE-2026-43284 / CVE-2026-43500) is a Linux kernel vulnerability disclosed in early May 2026. It allows any unprivileged user to escalate to root by exploiting a page cache overflow in the esp4, esp6, and rxrpc kernel modules.

The chain: one HTTP request → foothold in an nginx worker → Dirty Frag → root on the server. No credentials. No accounts. No traces in application logs.

This is not a theoretical attack. Both need to be patched now.

CHECK YOURSELF — THREE COMMANDS

First — nginx version. You need 1.30.1 or higher:

nginx -v

If you see something like nginx/1.24.0 or nginx/1.18.0 — you’re vulnerable.

Second — check whether your config contains vulnerable rewrite rules. Looking for rewrite directives with $1/$2 and a question mark:

grep -rn "rewrite" /etc/nginx/ | grep -E '\$[0-9].*\?'

Empty output — good. Non-empty — you have a vulnerable configuration. Patch immediately regardless of your nginx version (the patched version handles these configs safely, but it’s worth auditing either way).

Third — ASLR. Until you’ve patched, make sure it’s enabled:

cat /proc/sys/kernel/randomize_va_space

Should be 2. If it’s 0 or 1 — that means RCE with a single request. Enable it immediately:

echo 2 | sudo tee /proc/sys/kernel/randomize_va_space
echo "kernel.randomize_va_space = 2" | sudo tee /etc/sysctl.d/99-aslr.conf
sudo sysctl -p /etc/sysctl.d/99-aslr.conf

This doesn’t close the vulnerability — but it shifts the scenario from RCE to DoS. Significantly better.

PATCHING

Patching is the only complete fix. Configuration workarounds are not sufficient for production.

Debian/Ubuntu:

sudo apt update && sudo apt install nginx
nginx -v

Important: the official Ubuntu and Debian repositories sometimes lag a few days behind upstream. If apt gives you a version below 1.30.1 — connect the official nginx.org repository directly. This is safe: the GPG keys are verified.

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

echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \
http://nginx.org/packages/ubuntu $(lsb_release -cs) nginx" \
    | sudo tee /etc/apt/sources.list.d/nginx.list

sudo apt update && sudo apt install nginx

RHEL/AlmaLinux/Rocky Linux:

sudo dnf update nginx
nginx -v

After updating — always validate the config and reload. Occasionally after a major version bump nginx changes how certain directives behave and the config fails to validate:

sudo nginx -t && sudo systemctl reload nginx

nginx -t checks the config syntax. No errors — reload applies the new version without downtime. If there are errors — fix them before reloading.

IF YOU CAN’T PATCH RIGHT NOW

Sometimes production is locked down: a release window, a change freeze, an approval process. For those cases — a configuration mitigation.

The vulnerability requires two conditions simultaneously: an unnamed capture ($1, $2) and a question mark in the replacement string. Remove either one and the bug won’t trigger.

Option one — replace unnamed captures with named ones. Named captures take a different code path and don’t hit the vulnerable logic:

# Before — vulnerable (unnamed capture, question mark):
rewrite ^/api/(.*)$ /internal?id=$1 last;

# After — safe (named capture):
rewrite ^/api/(?P.*)$ /internal?id=${id} last;

(?P.*) is a named capture with the name id. ${id} references it by name. Functionally identical, but a different path through the code.

Option two — remove the rewrite with the question mark entirely and rewrite using proxy_pass:

# Instead of rewrite:
location ~ ^/api/(.+)$ {
    proxy_pass http://backend/internal?id=$1;
}

proxy_pass doesn’t go through ngx_http_rewrite_module — the vulnerability doesn’t apply.

This is a temporary measure. You still need to patch.

KUBERNETES AND INGRESS NGINX — A SPECIAL CASE

If you’re running Ingress NGINX Controller in Kubernetes — the situation is more complicated.

In March 2026, the Kubernetes project retired Ingress NGINX. The last release was v1.15.1. It contains the same ngx_http_script.c with the same vulnerability. And there will be no upstream patch — the project is closed.

The nature of an ingress controller makes this worse: it typically sits at the edge of the cluster, directly reachable from the public internet. NGINX Rift here is an unauthenticated vulnerability, reachable from anywhere, with no patch coming.

Migration options: nginx-gateway-fabric (the official successor from F5), Traefik, or Envoy. Not a quick task, but no longer optional.

WHY THIS ISN’T JUST ANOTHER CVE

nginx runs on roughly 34% of all web servers in the world. It sits in front of backend services, databases, APIs — it’s the first thing that touches every incoming HTTP request, before any other layer of protection. Cloudflare in front? WAF rules? Great. But requests from legitimate users still reach nginx — and one of them can be crafted.

What makes this CVE different from most:
— the bug lives in code that runs on every rewrite request
— no authentication required
— no special privileges required
— works over plain HTTP
— PoC has been public since day one

Updating nginx is not a “I’ll get to it this weekend” task. Exploitation is already happening.

Leave your thought here

Your email address will not be published. Required fields are marked *