
Detects the CVE-2026-42945 rewrite pattern in nginx configs: rewrite with ? in the replacement plus an unnamed capture consumed in the same location
Finds configurations vulnerable to CVE-2026-42945, the heap buffer overflow in nginx's ngx_http_rewrite_module.
Running an affected nginx version (0.6.27 through 1.30.0) is not enough to be exploitable. The configuration has to contain a specific pair of directives, and that pair is rare: two independent scans of real-world configs found zero exploitable ones out of 1465 and one out of 35633. This script tells you which side of that line you are on.
A rewrite whose replacement contains a ? with arguments after it, followed by an unnamed capture ($1 to $9) read in the same location:
location / {
rewrite ^(.*) /new?c=1; # the ? here raises the internal is_args flag
set $myvar $1; # ...which was never cleared, and leaks into this
return 200 $myvar;
}
One pass sizes the buffer for the raw string, the next copies an escaped version into it. Both halves look correct on their own.
Every rule below was measured on nginx 1.30.0 rather than inferred, because the published descriptions get two of them wrong.
Two consequences worth spelling out:
A trailing ? is not dangerous. /new/$1? is how you drop the inherited query string, and it appears in a large share of migration configs. Flagging every ? would shout at half the internet.
"Named captures are safe" is stated wrong. What matters is how the capture is read, not how it was declared. A named group read as $1 crashes exactly like an unnamed one.
Also ignored, for the same measured reasons: a ? inside the regex itself (a quantifier), and redirect / permanent / break, which end processing before the consumer runs.
nginx -T | python3 check_rewrite.py
python3 check_rewrite.py /path/to/dump.txt
python3 check_rewrite.py --json /path/to/dump.txt
Python 3, standard library only.
Exit codes: 0 clean, 1 a pair was found, 2 the config could not be read or parsed. The third one is the point: an unclosed quote or unbalanced braces truncate the analysis, and a checker that answers "nothing found" for a file it failed to read is worse than one that crashes. When that happens it says so and returns 2.
Feed it nginx -T, not individual files: includes can live anywhere, and a generated config is only true on the running server. Findings are reported against the file and line the directive actually lives in, not the offset in the dump.
$ python3 check_rewrite.py samples/vuln-nginx-T.txt
[HIGH] находка #1
location: location / (/etc/nginx/nginx.conf:14)
rewrite: rewrite ^(.*) /new?c=1 (/etc/nginx/nginx.conf:15)
захват $N: set -> set $myvar $1 (/etc/nginx/nginx.conf:16)
почему: обработка продолжается в этом же location без редиректа
Итого находок: 1
Printed at the end of every report as well, so nobody mistakes the tool for a guarantee:
location is reported;rewrite and set sitting directly in server rather than inside a location are not tracked;set $tmp $1; and then using $tmp) are not followed;try_files, error_page and named locations are not followed;if that never fires is still reported.Updating is more reliable than any config check. Take the current stable or mainline release from nginx.org.
python3 -m unittest test_check_rewrite -v
29 tests. Most of them are negative cases, since the risk in a tool like this is shouting at configs that are fine. It has also been run against a production config of 250 lines across eleven included files, with a map regex and a CSP header full of semicolons inside quotes: zero findings, zero parse complaints, and exactly one finding once a real pair was injected into it.
MIT
| Config inside one location | Crashes |
|---|
rewrite ^(.*) /new?c=1; + set $x $1; | yes |
rewrite ^/old/(.*)$ /new?x=1; + set $x $1; | yes |
rewrite ^/old/(.*)$ /new/$1?; + set $x $1; | no |
rewrite ... /mid?c=1; then rewrite ... /new; then set $x $1; | no |
rewrite ^(.*) /new?c=1 break; + set $x $1; | no |
rewrite ^(?<tail>.*) /new?c=1; + set $x $1; | yes |
rewrite ^(?<tail>.*) /new?c=1; + set $x $tail; | no |