NGINX Rift: The 18-Year RCE Just Went Fully Exploitable—Patch Your Servers Now
CRITICAL: A remote code execution vulnerability discovered in NGINX this week has been silently lurking in production for 18 years. It's pre-auth, remotely exploitable with a single HTTP request, and a working proof-of-concept exists. If you run NGINX anywhere — Plus, Ingress Controller, Gateway Fabric, App Protect — patch immediately.
The headline
An autonomous binary analysis system discovered five security issues in NGINX. Four were confirmed by F5. The critical one — CVE-2026-42945, dubbed NGINX Rift — is a heap buffer overflow in the rewrite directive handling that's been present since 2008.
This is not theoretical. A working exploit demonstrating unauthenticated remote code execution exists and was shared with F5 before public disclosure. With ASLR disabled (common on many servers), this becomes a one-click RCE. With ASLR enabled, it's still a reliable denial-of-service.
Affected versions: NGINX Open Source 0.6.27 through 1.30.0 (~18 years of releases), NGINX Plus R32 through R36, and every derivative product (Instance Manager, App Protect, Ingress Controller, Gateway Fabric, etc.).
The vulnerability in plain terms
NGINX uses rewrite and set directives to modify request URIs and store variables:
location ~ ^/api/(.*)$ {
rewrite ^/api/(.*)$ /internal?migrated=true; # <-- The dangerous pattern
set $original_endpoint $1; # <-- Captures from the regex
}
A request like /api/++++++ (six plus signs) triggers the overflow. Here's why:
The script engine uses a two-pass design:
- Pass 1 (length calculation): Compute how many bytes are needed. Uses a fresh sub-engine initialized with
is_args = 0. - Pass 2 (copy): Allocate that many bytes and copy the data. Uses the main engine where
is_args = 1(set by the earlier rewrite's?).
The state mismatch is the bug. In pass 1, the engine calculates the raw length of the capture: 6 bytes for ++++++.
In pass 2, because is_args = 1, the engine escapes each plus sign: + becomes %2B (3 bytes each). It writes 18 bytes into a 6-byte buffer.
Heap buffer overflow. Game over.
Length calculation pass: allocate for 6 bytes
Copy pass: write 18 bytes (plus signs escaped)
Result: +12 bytes overflow, attacker-controlled data
Why this matters: the exploit chain
The bytes are attacker-shaped
Unlike a typical heap overflow where random bytes spill out, here the overflow data comes directly from your HTTP request. You control every byte that overflows. This makes it exploitable, not just a crash.
Exploitation: heap feng shui + cleanup pointer hijacking
The attacker uses three techniques:
1. Cross-request heap feng shui
- Open connection A, send partial headers
- Open connection B (pools allocated adjacent in memory)
- Complete A's headers with the overflow payload → corrupts B's pool
- Close B immediately → B's pool is destroyed before the corruption causes further issues
- Worker process stays alive; new requests inherit the same heap layout
2. Spray fake cleanup structures via POST NGINX pool destruction walks a cleanup linked list and executes handler functions. The attacker sprays the heap with POST request bodies containing fake cleanup structures pointing to &libc_system with command arguments.
3. Overwrite the cleanup pointer The overflow writes just enough bytes to redirect the cleanup pointer to one of the sprayed structures. On pool destruction, NGINX calls handler(data), executing /bin/sh -c <attacker command>.
Result: Code execution as the NGINX worker process (usually nobody, but can be root in misconfigured setups).
The deterministic memory layout helps the attacker
NGINX forks workers from a master process, so every worker has identical heap layout. If an exploit attempt crashes a worker, the master spawns a new one with the same memory layout. The attacker can retry until it succeeds — essentially a free brute-force oracle.
Detection: are you vulnerable right now?
Run this on every NGINX server:
# Check version
nginx -v
# Check for the vulnerable pattern (rewrite with ? followed by set with capture)
grep -rE '^\s*rewrite\s+.*\$[0-9].*\?' /etc/nginx/
Any output from the second command is a candidate vulnerable rule. Example patterns:
rewrite ^/old/(.*)$ /new.php?id=$1 last;
rewrite ^/api/v1/(.*)$ /v2/$1?legacy=true;
rewrite ^/(.+)$ /index.php?page=$1;
If you're on any version between 0.6.27 and 1.30.0, assume you're vulnerable.
Fixed versions (patch NOW)
| Product | Fixed in |
|---|---|
| NGINX Open Source | 1.30.1, 1.31.0 |
| NGINX Plus R32–R36 | R32 P6, R36 P4 |
| NGINX Instance Manager 2.16.0–2.21.1 | See F5 advisory |
| F5 WAF for NGINX 5.9.0–5.12.1 | See F5 advisory |
| NGINX App Protect WAF 4.9.0–4.16.0, 5.1.0–5.8.0 | See F5 advisory |
| NGINX App Protect DoS 4.3.0–4.7.0 | See F5 advisory |
| NGINX Gateway Fabric 1.3.0–1.6.2, 2.0.0–2.5.1 | See F5 advisory |
| NGINX Ingress Controller 3.5.0–3.7.2, 4.0.0–4.0.1, 5.0.0–5.4.1 | See F5 advisory |
There is no patch for versions 0.6.27–0.9.7. Upgrade if you're running something that old.
Emergency mitigation (if you can't patch immediately)
Replace unnamed captures with named captures throughout your config. The vulnerability requires unnamed $N captures specifically:
# VULNERABLE
rewrite ^/old/(.*)$ /new.php?id=$1 last;
# SAFE
rewrite ^/old/(?<page>.*)$ /new.php?id=$page last;
After editing:
nginx -t && systemctl reload nginx
⚠️ This is a workaround, not a fix. Patch as soon as your maintenance window allows.
Upgrade NOW
Debian/Ubuntu:
apt update && apt install --only-upgrade nginx
nginx -v
RHEL/AlmaLinux:
dnf upgrade nginx
nginx -v
cPanel users: WHM → EasyApache 4 → rebuild with latest NGINX modules.
Kubernetes/Docker: Rebuild images from nginx:1.30.1-alpine or nginx:1.31.0-alpine.
Verify after upgrade:
nginx -V 2>&1 | head -1 # Confirm new version
curl -sI http://localhost | grep Server
systemctl status nginx
Then re-run the vulnerability scan:
grep -rE '^\s*rewrite\s+.*\$[0-9].*\?' /etc/nginx/
Still matches? You're safe now (patched binary handles it), but your config still has the pattern. Consider refactoring to named captures for clarity.
The three other CVEs (also patched)
CVE-2026-42946 (CVSS 8.3): Memory allocation overflow in SCGI/uWSGI modules. Requires MITM. Only relevant if you use scgi_pass or uwsgi_pass.
CVE-2026-40701 (CVSS 6.3): Use-after-free in SSL module. Requires both ssl_verify_client on and ssl_ocsp on. mTLS setups affected.
CVE-2026-42934 (CVSS 6.3): Out-of-bounds read in charset module. Requires specific directives (charset, source_charset, charset_map, proxy_pass with buffering off).
All three are lower severity than Rift but patch them in the same window.
Hardening: defense in depth
Even patched, defense-in-depth saves you when the next pre-auth RCE drops:
1. Verify ASLR is enabled (turns RCE into DoS)
cat /proc/sys/kernel/randomize_va_space # Should be 2
2. Sandbox NGINX with systemd hardening
[Service]
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
SystemCallFilter=@system-service
SystemCallFilter=~@privileged @resources
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
NoNewPrivileges=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
3. WAF or reverse proxy in front ModSecurity, Cloudflare, or Varnish can catch crafted URIs before they reach NGINX.
4. Network segmentation Restrict NGINX access to legitimate sources only. Internal-only NGINX behind a firewall reduces exposure.
5. Run NGINX as non-root Even if exploited, code execution runs as the NGINX user, not root.
Timeline
- 4/18/2026: Autonomous analysis reports 5 memory corruption issues
- 4/21/2026: Responsible disclosure to NGINX
- 4/24/2026: NGINX confirms 4 issues
- 4/28/2026: Working RCE PoC completed
- 5/5/2026: PoC and video shared with NGINX
- 5/13/2026: Public disclosure + security advisory released
References
- F5 NGINX Security Advisory (CVE-2026-42945)
- depthfirst Technical Analysis & PoC
- NGINX Changelog: https://nginx.org/en/CHANGES
- Related: CVE-2026-42946, CVE-2026-40701, CVE-2026-42934
Bottom line: If you haven't patched NGINX in the last 24 hours, do it now. This is critical, pre-auth, and fully exploitable. There's no excuse for delay.
The discovery
An autonomous binary analysis system was tasked with analyzing the NGINX source repository. In six hours of analysis, it identified five security issues, including a critical heap buffer overflow in the rewrite directive handling. After responsible disclosure and further exploitation research, four issues were confirmed by F5/NGINX.
The headline vulnerability, CVE-2026-42945, is the most severe: a heap buffer overflow in ngx_http_rewrite_module that's been present since 2008 and is reliably exploitable for remote code execution (with ASLR disabled).
The vulnerability mechanics
Why rewrite and set directives matter
The rewrite directive modifies request URIs using regex-based pattern matching:
rewrite ^/old/api/(.*)$ /new/api/$1 last;
The set directive stores values in custom variables, often capturing groups from the most recent regex:
set $original_path $1;
Together, they're essential for API gateway configurations where you need to route to new endpoints while preserving the original request context for logging or backend processing.
The two-pass architecture
NGINX's script engine optimizes memory allocation with a two-pass design:
- Length calculation pass: Walk through the directive operations, computing the exact bytes needed for the final string
- Copy pass: Allocate exactly that many bytes and copy the data in
This avoids multiple small allocations, but it requires the length calculated in pass 1 to exactly match the bytes written in pass 2. If the engine state changes between passes, the allocations mismatch and heap corruption occurs.
The state mismatch: is_args flag not reset
The bug lives in src/http/ngx_http_script.c. When a rewrite directive contains a ? character in its replacement string, the engine sets an is_args flag:
void
ngx_http_script_start_args_code(ngx_http_script_engine_t *e)
{
e->is_args = 1;
e->args = e->pos;
e->ip += sizeof(uintptr_t);
}
This flag is never reset. It persists in the main engine state across subsequent directives.
When a set directive references a regex capture group, the engine runs the ngx_http_script_complex_value_code function, which creates a fresh sub-engine for the length calculation:
void
ngx_http_script_complex_value_code(ngx_http_script_engine_t *e)
{
ngx_http_script_engine_t le;
// ... code ...
ngx_memzero(&le, sizeof(ngx_http_script_engine_t)); // fully zeroed
// le.is_args = 0 (the sub-engine is fresh)
// but e->is_args = 1 (the main engine state persists)
// Length calculation happens with le.is_args = 0
}
The sub-engine le starts from zero. During the length calculation phase, this condition is checked:
size_t
ngx_http_script_copy_capture_len_code(ngx_http_script_engine_t *e)
{
if ((e->is_args || e->quote)
&& (e->request->quoted_uri || e->request->plus_in_uri))
{
// With le.is_args = 0, this is FALSE
return cap[n + 1] - cap[n]; // return raw, unescaped length
}
}
With le.is_args = 0, the condition evaluates to false. The length calculation assumes no escaping is needed and returns the raw capture length.
But during the copy phase, the main engine is used:
void
ngx_http_script_copy_capture_code(ngx_http_script_engine_t *e)
{
if ((e->is_args || e->quote)
&& (e->request->quoted_uri || e->request->plus_in_uri))
{
// With e->is_args = 1 (from the earlier rewrite),
// this condition is TRUE
// ngx_escape_uri expands characters:
// '+' becomes '%2B' (1 byte → 3 bytes)
// '&' becomes '%26' (1 byte → 3 bytes)
e->pos = (u_char *) ngx_escape_uri(pos, &p[cap[n]],
cap[n + 1] - cap[n],
NGX_ESCAPE_ARGS);
}
}
Now the copy phase sees is_args = 1 (from the earlier rewrite), so it escapes the capture. The ngx_escape_uri function expands each escapable character from 1 byte to 3 bytes.
The overflow: The buffer was allocated for the unescaped length (raw_size), but ngx_escape_uri writes raw_size + 2*N bytes, where N is the number of escapable characters. The data spills past the heap boundary.
Trigger configuration
location ~ ^/api/(.*)$ {
rewrite ^/api/(.*)$ /internal?migrated=true;
set $original_endpoint $1;
}
When a request like /api/++++++ arrives:
- The
rewritedirective matches and has?in the replacement, settingis_args = 1 - The
setdirective runs, referencing$1(the six plus signs) - Length calculation uses a fresh sub-engine with
is_args = 0, allocates 6 bytes - Copy phase uses the main engine with
is_args = 1, escapes to%2B, writing 18 bytes into a 6-byte buffer - Heap buffer overflow
Exploitation: overflow to RCE
Challenge: URI-safe bytes only
The attacker's input is constrained: it must survive the URI parser and escaping function. This means:
- No null bytes (terminate URI strings)
- No control characters
- Limited to URI-safe bytes and their escaped forms
How do you write a pointer with no null bytes?
Technique 1: Heap feng shui for deterministic timing
NGINX spawns worker processes from a master, creating identical heap layouts per worker. If an exploit crashes one, a new one spawns with the same memory layout — perfect for retrying.
The goal: overflow from one request's pool into the adjacent request's pool, then destroy only the victim pool before its corrupted state is used.
- Open connection A, send partial headers → NGINX allocates a request pool for A
- Open connection B → NGINX allocates its pool adjacent to A's pool in memory
- Complete A's headers with a crafted request triggering the rewrite overflow → overflows from A's pool into B's pool
- Immediately close B → triggers pool destruction before any of B's corrupted pointers are dereferenced
This timing ensures B's pool metadata is corrupted only in the cleanup pointer, and B is destroyed before the corruption causes further issues.
Technique 2: Target the cleanup list
NGINX manages allocations using ngx_pool_t structures. On pool destruction, it walks a cleanup linked list and executes handler functions:
struct ngx_pool_s {
ngx_pool_data_t d;
size_t max;
ngx_pool_t *current;
ngx_chain_t *chain;
ngx_pool_large_t *large;
ngx_pool_cleanup_t *cleanup; // The target pointer
ngx_log_t *log;
};
typedef void (*ngx_pool_cleanup_pt)(void *data);
struct ngx_pool_cleanup_s {
ngx_pool_cleanup_pt handler; // Function pointer
void *data; // Argument to handler
ngx_pool_cleanup_t *next;
};
On pool destruction:
for (c = pool->cleanup; c; c = c->next) {
if (c->handler) {
c->handler(c->data); // RCE point here
}
}
The goal: overwrite the cleanup pointer to reference a fake ngx_pool_cleanup_s structure with handler = &libc_system and data = pointer to "/bin/sh -c <command>".
Technique 3: POST body spraying
Overflowing with URI-safe characters can only corrupt specific bytes. To inject arbitrary binary data (including null bytes in function pointers), the attacker sprays the heap with POST request bodies:
POST /path HTTP/1.1
Content-Length: 2048
[Binary payload containing fake ngx_pool_cleanup_s structures
with handler = system function address
data = pointer to command string]
POST bodies are raw binary data, unrestricted by URI parsing or escaping. Due to the deterministic heap layout, they land at predictable offsets. The overflow writes just the cleanup pointer (3-4 bytes) with a value pointing to one of these sprayed structures.
Technique 4: Finding URI-safe pointer values
The overflow bytes pass through the escaping function, which limits which pointer values can be written intact. The attacker can brute-force to find heap offsets where the fake structures land at addresses that survive escaping (e.g., 0x7f2b2b2b contains bytes that remain unchanged after URI escaping).
By spraying multiple fake cleanup structures and checking which land at "safe" addresses, the attacker reliably points the cleanup pointer to their payload.
Proof of concept
The PoC demonstrates:
- Open HTTP connection A with partial headers
- Open HTTP connection B (allocates its pool adjacent to A's)
- Complete A's headers with a crafted request, triggering the rewrite overflow from A's pool into B's
- Close B immediately → triggers
ngx_destroy_poolon B, which executes the corrupted cleanup handler - The handler runs
/bin/sh -c <attacker command>, achieving code execution
A video demonstrating the PoC was shared with NGINX before public disclosure.
Affected versions
- NGINX Open Source: 0.6.27 through 1.30.0 (fixed in 1.30.1, 1.31.0)
- NGINX Plus: R32 through R36 (fixed in R32 P6, R36 P4)
- NGINX Instance Manager: 2.16.0 through 2.21.1
- F5 WAF for NGINX: 5.9.0 through 5.12.1
- NGINX App Protect WAF: 4.9.0–4.16.0, 5.1.0–5.8.0
- NGINX App Protect DoS: 4.3.0 through 4.7.0
- F5 DoS for NGINX: 4.8.0
- NGINX Gateway Fabric: 1.3.0–1.6.2, 2.0.0–2.5.1
- NGINX Ingress Controller: 3.5.0–3.7.2, 4.0.0–4.0.1, 5.0.0–5.4.1
Hardening recommendations
Even with ASLR enabled, this remains a reliable DoS. Defense-in-depth approach:
- Patch now — upgrade to fixed NGINX versions immediately (1.30.1, 1.31.0, or equivalent Plus versions)
- Verify ASLR is enabled — check
cat /proc/sys/kernel/randomize_va_spacereturns2. Anything else turns this from DoS into RCE. - Sandbox NGINX — restrict via systemd hardening directives:
[Service]
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
SystemCallFilter=@system-service
SystemCallFilter=~@privileged @resources
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
NoNewPrivileges=true
- WAF or caching reverse proxy in front — a WAF or cache layer (Varnish, Cloudflare, ModSecurity) can validate request URIs and reject crafted payloads before they reach NGINX.
- Audit rewrite configurations — identify any
rewritedirectives with?in the replacement string followed bysetdirectives referencing captures. These are the exploitation pattern. - Network segmentation — if NGINX only handles internal traffic, restrict network access at the firewall. This won't stop an internal attacker but limits the exposure surface.
Timeline
- 4/18/2026: Autonomous analysis reports 5 memory corruption issues in NGINX
- 4/21/2026: Responsible disclosure to NGINX via GitHub security advisory
- 4/24/2026: NGINX confirms 4 of the 5 reported issues
- 4/28/2026: Researchers notify NGINX that a working PoC demonstrating RCE exists
- 5/5/2026: RCE PoC and demo video shared with NGINX
- 5/13/2026: F5 releases public security advisory; research team publishes technical writeup
References
- F5 NGINX Security Advisory (CVE-2026-42945)
- depthfirst Technical Writeup and PoC
- NGINX Changelog: https://nginx.org/en/CHANGES