# Needle in a haystack: measuring the impact of two nginx RCEs

Two nginx rewrite-engine heap overflows from stale is_args flag and capture-budget undercount; a GitHub-wide scan found one real vulnerable config.

- Payout: Research
- Severity: critical
- Program: nginx
- Write-up by: Calif
- Disclosed: 2026-05-29
- CVE: CVE-2026-42945, CVE-2026-9256
- Write-up quality: 8/10
- Original: https://blog.calif.io/p/needle-in-a-haystack-measuring-the
- Library page: https://bugbountylibrary.com/r/blog%3Ablog-calif-io-p-needle-in-a-haystack-measuring-the
- Tags: area:cloud domain:infra impact:dos kind:research platform:research sev:critical tech:nginx technique:incomplete-fix technique:recon technique:source-review vuln:dos vuln:memory-corruption

**Target:** nginx rewrite module script engine (ngx_http_script_*)
**Root cause:** The rewrite script engine sizes buffers in a length pass and fills them in a copy pass that disagree: CVE-2026-42945 leaves e->is_args=1 after a flagless rewrite with ?, so a later set/if/rewrite copies captures URI-escaped (3x) into a raw-sized buffer; CVE-2026-9256 budgets escape space once over the whole URI but escapes overlapping nested capture groups once per $N reference.
**Steps:**
1. CVE-2026-42945 set case: `location ~ ^/api/(.*)$ { rewrite ^/api/(.*)$ /internal?migrated=true; set $original_endpoint $1; }`
2. Send a request whose captured portion contains `+` (escapable) so quoted_uri/plus_in_uri is set, e.g. `/api/++++...`
3. Length pass (zeroed sub-engine) sizes raw bytes; copy pass on main engine with stale is_args=1 applies ngx_escape_uri writing up to 3x -> heap overflow
4. if case: `if ($request_method = $1) { return 204; }` after the flagless rewrite hits same ngx_http_rewrite_value path; regex operators (~,~*) are safe
5. rewrite-chain case: `rewrite ^/(.*)$ /stage/$1?x=1;` then `rewrite ^/stage/(.*)$ /destination/$1 break;` — second rewrite's fast path sizes raw, copies escaped
6. CVE-2026-9256: `rewrite ^/((.*))$ http://backend/$1$2 redirect;` with nested groups and escaping-triggering replacement overflows budget
**How found:** Built ngxray, a static scanner reusing nginx's own tokenizer (ngx_conf_read_token/ngx_conf_parse) with a patched ngx_conf_handler emitting a JSON syntax tree; ran 100+ GitHub Code Search queries across .conf, Dockerfiles, heredocs, Jinja/ERB templates, K8s ConfigMaps and Markdown, extracted 35,633 parseable configs, and applied JSON rules (max_args:2 for no-flag, ordered:true, overlapping_refs PCRE check) to find vulnerable directive orderings.
**Exploitation:** Unauthenticated remote request to a vulnerable nginx config triggers heap buffer overflow in the rewrite phase; demonstrated under AddressSanitizer (heap-buffer-overflow in ngx_escape_uri, ngx_string.c:1689). Leads to worker crash/DoS or potentially RCE.
**Insight:** When auditing config-driven engines, look for state flags set in one directive and not reset before the next (is_args leaking across flagless rewrites and if{} boundaries), and for length-vs-copy pass mismatches where escaping (3x) is budgeted once but applied per reference — grep for flagless rewrites containing ? followed by set/if using $1-$9.

## Proof of concept (copied exactly from the original report)

**Vulnerable nginx config with flagless rewrite and set**

```
location ~ ^/api/(.*)$ {
 rewrite ^/api/(.*)$ /internal?migrated=true;
 set $original_endpoint $1; # $1 copied with stale is_args=1
}
```

**if-case trigger with capture on RHS**

```
location ~ ^/api/(.*)$ {
 rewrite ^/api/(.*)$ /internal?migrated=true;
 if ($request_method = $1) { # $1 on the right-hand side hits the same bug
 return 204;
 }
}
```

**Rewrite-chain trigger config**

```
location / {
 rewrite ^/(.*)$ /stage/$1?x=1; # sets is_args, no flag
 rewrite ^/stage/(.*)$ /destination/$1 break; # $1 sized raw, copied escaped
}
```

