[overlapping_captures] Rewrite overlapping captures overflow buffer in redirect/args context (CVE-2026-9256)
CVE-2026-9256 (nicknamed "nginx-poolslip") is a remote code execution vulnerability affecting nginx. In certain rewrite configurations on unpatched versions, an unauthenticated attacker can trigger a heap buffer overflow in a nginx worker process by sending a crafted HTTP request.
What this check looks for
This plugin flags a rewrite directive where all of the following are true:
- The regex has two captures (named or unnamed) where one is nested inside the other, for example
^/((.*))$or^/(?<outer>(.*))$, and the replacement references both by$N(with neither$Nrepeated). - The replacement contains no nginx variables other than the
$Nreferences (no$host,${uri}, etc.). - The captures end up URI-escaped on output — any of:
- an explicit
redirectorpermanentflag, - the replacement starts with
http://orhttps://(implicit redirect), or - the replacement contains a
?(other than a sole trailing one, which nginx strips) and at least two of the referenced$Nappear after it.
- an explicit
Because Gixy-Next cannot determine the nginx version from the configuration, any matching pattern is reported as INFORMATION rather than a warning — if you are already on a patched version, no action is required.
Why this is a problem
On unpatched nginx, a replacement with no variables and no duplicate $N makes the worker pre-compute the output buffer size from the regex captures and the URI, but the formula only counts the URI's escape size once. With overlapping captures, two $N references can match the same input bytes, so the captures together are longer than the URI and the URI-based escape budget is too small.
In redirect or arguments context each capture is URI-escaped on write (each + becomes %2B, each space becomes %20, etc.), so the actual output can be several times the planned buffer. A crafted request like /++++++++++++++++++++++++++++++ against rewrite ^/((.*))$ http://x/$1$2 redirect; overruns the worker's heap.
Fix and workaround
Upgrade to nginx 1.30.2+ (stable) or 1.31.1+ (mainline).
If you cannot upgrade immediately, remove the $N references from the replacement: convert the captures to named ones ((?<name>...)) and reference them by $name rather than $N. PCRE numbers named captures alongside unnamed ones, so $1 still works for (?<name>...) — renaming the regex group alone is not enough.
Bad configurations
Explicit redirect flag:
location / {
rewrite ^/((.*))$ http://127.0.0.1:8080/$1$2 redirect;
}
Implicit redirect from the http:// prefix, no flag needed:
location / {
rewrite ^/((.*))$ http://127.0.0.1:8080/$1$2;
}
? in the replacement:
location / {
rewrite ^/((.*))$ http://127.0.0.1:8080/?$1$2;
}
Named outer capture still triggers the bug if both captures are referenced by $N:
location / {
rewrite ^/(?<outer>(.*))$ http://example.com/$1$2 redirect;
}
Better configuration
location / {
rewrite ^/(?<path>.*)$ http://127.0.0.1:8080/$path redirect;
}
Additional notes
- Sibling captures alone (
(.*)/(.*)) are fine — they cover non-overlapping parts of the input. - Cross-pair
$Nreferences like^/((.*))/((.*))$ /foo/$1$3are fine —$1and$3are siblings even though each pair is nested. - A replacement that references only one
$Nis fine — the bug needs at least two. - A replacement containing any nginx variable, or repeating the same
$N, takes a different length-calc path and is fine. - A
?only at the end of the replacement is stripped by nginx and does not trigger the args path. - Affected versions: NGINX Open Source 0.1.17–1.30.1 and 1.31.0 (fixed in 1.30.2/1.31.1), NGINX Plus R32–R36 (fixed in R32 P7 / R36 P5), NGINX Plus R37 (fixed in R37.0.1.1).
- For more information see CVE-2026-9256 on NVD.