Yesterday F5 disclosed CVE-2026-42945, a heap buffer overflow in NGINX's ngx_http_rewrite_module. CVSS 9.2. Unauthenticated RCE. Every version from 0.6.27 through 1.30.0, meaning the bug has been sitting in the codebase since 2008. Eighteen years.
The security Twitter crowd ran with "19 million NGINX servers exposed" and the takes got predictably breathless from there.
So this morning I checked our own sites. They're fine. And yours probably are too, for reasons that don't fit in a tweet.
What the Bug Actually Is
NGINX's script engine processes rewrite rules in two passes. First it calculates how much buffer space it needs (the length pass), then it copies data into that buffer (the copy pass). The two passes use different engine states.
When a rewrite directive contains a ? in its replacement string, the engine sets an internal flag called is_args to 1. This flag stays set for subsequent set directives in the same block. Here's the problem: the length-calculation pass runs on a freshly zeroed sub-engine where is_args is 0, so it measures the raw capture length without URI escaping. But the copy pass runs on the main engine where is_args is 1, so it applies ngx_escape_uri with NGX_ESCAPE_ARGS, expanding each escapable byte from 1 to 3 bytes.
Result: the copy writes raw_size + 2*N bytes into a raw_size buffer, where N is the number of escapable characters the attacker puts in the URL. Classic heap overflow, attacker-controlled data, and the overflow size scales with the request.
The Config That Triggers It
Not every NGINX server is vulnerable. You need a specific configuration pattern: a rewrite directive with ? in the replacement, followed by a set directive that captures from the same regex. Something like:
location ~ ^/api/(.*)$ {
rewrite ^/api/(.*)$ /internal?migrated=true;
set $original_endpoint $1;
}
Both pieces are required. The rewrite with ? flips the is_args flag. The set with a capture group ($1) triggers the mismatched length calculation. Without both, the bug doesn't fire.
This isn't a rare pattern, but it's not universal either. Plenty of NGINX configs use rewrites. Plenty use set. The specific combination of both, with a ? in the rewrite target, is the narrow gate.
How to Check Your Config in 30 Seconds
Run this:
nginx -T 2>&1 | grep -P 'rewrite\s+.*\?' | head -20
If nothing comes back, you don't have a rewrite with ? in the replacement and you're not vulnerable to this CVE.
If you get hits, check whether there's a set directive in the same location block that references a capture group. If both conditions are true, you should patch now.
If you're behind Cloudflare, AWS ALB, or any other reverse proxy that terminates before hitting NGINX, the attacker's crafted URI may not reach your NGINX at all, but don't rely on that. Patch anyway.
And if you're not running NGINX at all? Move on with your day. We're behind Cloudflare with no NGINX in the stack, so this one was a non-event for us.
The Exploit Chain
DepthFirst published a full proof-of-concept, and the exploitation technique is genuinely clever. It uses cross-request heap feng shui: the attacker sprays POST bodies containing fake ngx_pool_cleanup_t structures into the worker's heap, then triggers the overflow to corrupt an adjacent connection pool's cleanup pointer. When NGINX destroys that pool, it walks the cleanup list and calls whatever function pointer is there. The attacker's fake structure points to system() with their command string.
But there's a catch. The PoC disables ASLR (echo 0 > /proc/sys/kernel/randomize_va_space). With ASLR on (which is the default on every modern Linux, BSD, and macOS system), the attacker doesn't know where system() lives in memory. The heap spray addresses also need to consist entirely of URI-safe bytes to survive NGINX's URL parsing, which further constrains the attack. DepthFirst's writeup describes the technique as working through deterministic heap layout after worker fork, which is plausible in theory, but the published PoC only works with ASLR off.
That doesn't mean it's unexploitable with ASLR on. Someone may find an info leak or a more reliable heap grooming technique. But the gap between "PoC with ASLR disabled on a lab setup" and "reliable exploit against a production server with ASLR, PIE, and stack canaries" is real.
The "19 Million Servers" Number
This is the part that bothers me.
The 19 million figure comes from Shodan scanning for NGINX version banners. That tells you how many servers run NGINX. It tells you nothing about how those servers are configured. Shodan can't see your nginx.conf. It can't tell whether you have a rewrite with ? followed by a set with a capture group. It can't tell whether ASLR is on.
So "19 million servers" really means "19 million servers that return an NGINX version string in the range affected by this CVE." How many of those have the vulnerable config pattern? Nobody knows. It could be 5% or 0.5%. Could be less. The number sounds alarming because it's meant to.
Don't get me wrong: this is a real bug, the research is excellent, and you should patch. DepthFirst's autonomous system found five vulnerabilities in NGINX in six hours, including this one, and the technical writeup on the two-pass engine flaw is worth reading on its own. But "CVSS 9.2" and "19 million servers" in the same sentence creates an impression that doesn't match the actual attack surface.
What to Do
Patch to NGINX 1.31.0 or 1.30.1. If you're on NGINX Plus, grab R36 P4, R35 P2, or R32 P6. F5's advisory is K000161019.
If you can't patch immediately, audit your configs for the rewrite ? + set $capture pattern and remove or restructure the blocks that match. Splitting the rewrite and set into separate processing phases, or dropping the ? from the rewrite target, should break the trigger chain.
And next time someone tweets "X million servers are vulnerable" about a config-dependent bug, check what the scanner actually measured before forwarding it.
Enjoyed this post?
Get notified when I publish something new. No spam, unsubscribe anytime.