Tech 7 min read

nginx CVE-2026-8711 (njs) and CVE-2026-9256 (rewrite) after Rift

IkesanContents

TL;DR

CVE-2026-8711 (njs) NGINX JavaScript 0.9.4–0.9.8 where js_fetch_proxy is configured with a client-controlled NGINX variable ($http_* / $arg_* / $cookie_*) and combined with a location that calls ngx.fetch(). Fixed in njs 0.9.9.

CVE-2026-9256 (rewrite) nginx 0.1.17–1.31.0 where ngx_http_rewrite_module has rewrite rules with overlapping captures (e.g. ^/((.*))$) and a substitution string that references multiple captures without a variable. Fixed in nginx 1.30.2 / 1.31.1.

What’s common Both are pre-auth, both trigger a worker-process heap buffer overflow (CWE-122), and both reach RCE depending on ASLR. CVSS v4.0 9.2 CRITICAL.


I thought CVE-2026-42945 (NGINX Rift) on May 13 was the end of the May 2026 nginx story. It wasn’t — two more came out in the following two weeks.
On May 19, the NGINX JavaScript (njs) module disclosed CVE-2026-8711. On May 22, the rewrite module disclosed another heap buffer overflow, CVE-2026-9256.
Both are CVSS v4.0 9.2 CRITICAL, both pre-auth.

These are independent CVEs, not part of the Rift 4-CVE chain. Upgrading to the Rift-fixed versions (1.30.1 / 1.31.0) does not close CVE-2026-8711 or CVE-2026-9256 — they need their own patches.
The trigger conditions differ, so handle them separately.

CVE-2026-8711 — njs breaks when js_fetch_proxy interpolates a client variable

NGINX JavaScript (njs) is an extension that lets you call JavaScript from nginx configuration via ngx_http_js_module.
The js_fetch_proxy directive in njs causes a worker-process heap buffer overflow when it’s configured with a URL containing a client-controlled variable, in combination with a location that calls ngx.fetch().

Pulling from the NVD wording, the trigger is “js_fetch_proxy configured with at least one client-controlled NGINX variable.”
That means $http_* (request headers), $arg_* (query string), $cookie_* (cookies) — variables the attacker writes freely via the HTTP request.

# Illustrative only — do not use as-is.
location /proxy {
    js_fetch_proxy http://upstream.example.com$arg_target;
    js_content fetchHandler;
}

A client-writable variable like $arg_target placed into the js_fetch_proxy argument, with ngx.fetch() called downstream, is the vulnerable shape.
Setups that use nginx purely as a “dynamic upstream router” via js_fetch_proxy are the realistic match.

CVSS v4.0 9.2 CRITICAL, v3.1 8.1 HIGH (CWE-122).
Base impact is a worker-process crash (DoS); with ASLR off, or with attacker-controlled ASLR bypass, it reaches arbitrary code execution in the worker context.
The severity model matches Rift.

Fixed in njs 0.9.9.
Scope is NGINX OSS and NGINX Plus data plane only. F5’s BIG-IP / BIG-IQ / control plane are out of scope (F5 advisory K000161307).
NGINX Plus needs a separate hotfix per R-version, so check F5’s advisory for your specific R-release.

If you’re not using njs you’re basically clear, but on distributions that ship the njs module in the nginx package, the binary is present even when load_module isn’t called in config. Run nginx -V to check for --with-http_js_module, and grep your config for load_module modules/ngx_http_js_module.so;.
If njs isn’t loaded, there’s no attack path.

CVE-2026-9256 — rewrite with “overlapping captures” leaves the allocated buffer short

nginx 1.30.2 stable / 1.31.1 mainline (released 2026-05-22) fixed another heap buffer overflow in ngx_http_rewrite_module.
It’s a residual that survived the Rift fix.

The 1.31.1 changelog states:

*) Security: a heap memory buffer overflow might occur in a worker process when using a configuration with overlapping captures in ngx_http_rewrite_module, potentially resulting in arbitrary code execution (CVE-2026-9256). Thanks to Mufeed VH of Winfunc Research.

The keyword is overlapping captures.
A PCRE regex like ^/((.*))$ — where outer and inner parens are both capturing groups — combined with a substitution string that references multiple captures (e.g. $1$2) and contains no variables, produces a case where the allocated buffer is smaller than the post-escape write size.

# Illustrative only — shows the trigger shape.
rewrite ^/((.*))$ /target/$1$2 last;

Outer (.*) is $1, inner (.*) is $2, and the substitution references both. A pattern you’d rarely write by hand, but configs auto-generated from .htaccess, CMS admin panels, or Ingress-Controller annotations rendered into the final nginx.conf can carry it.

The severity rating is split.
nginx.org marks it medium; F5/NVD’s CVSS v3.1 is 8.1 HIGH and v4.0 is 9.2 CRITICAL.
Same structure as the Rift article: nginx side weights “config-conditional, ASLR-dependent,” while CVSS weights “pre-auth, network-reachable, high C/I/A.”

Affected range is 0.1.17 through 1.31.0.
That means even the Rift-fixed 1.31.0 is vulnerable; you need 1.31.1 / 1.30.2.
NGINX Plus is fixed in R37 → 37.0.1.1, R36 → R36 P5, R32 → R32 P7 (per secondary sources; confirm via F5 K000161377).

Nebula Security posted a PoC teaser on May 20, but as of this writing (2026-05-26) no full public PoC is confirmed for CVE-2026-9256.
In-the-wild exploitation is reported by secondary sources but mixed with Rift; for 9256 alone, ITW is not yet confirmed.
Given Rift was observed in VulnCheck’s honeypots, when a full PoC drops the same opportunistic-scanning pattern is likely to start.

Verification order

Same order as the Rift article: vulnerable config present → public reachability → ASLR status.
For the nginx instance itself, dump the expanded config via nginx -T and grep for these three things.

# 1. njs js_fetch_proxy with a client-controlled variable
nginx -T 2>/dev/null | grep -E 'js_fetch_proxy.*\$(http_|arg_|cookie_)'

# 2. Is the njs module loaded at all?
nginx -T 2>/dev/null | grep -E 'load_module.*ngx_http_js_module'

# 3. rewrite with overlapping captures + multi-capture reference
nginx -T 2>/dev/null | grep -E 'rewrite.*\(\(.*\)\).*\$[0-9]+\$[0-9]+'

The third grep is a shape filter only — when it matches, eyeball the config to verify it’s actually “overlapping captures + variable-less substitution.”
nginx -t alone misses snippets loaded via include, so always use nginx -T (capital T, full config dump).

For environments where nginx config is generated by a WAF product or Ingress Controller, looking at the upstream config (CRDs, Helm values, templates) isn’t enough.
You need to dump the actual running nginx.conf from inside the Pod.

Why so many holes from the same place

Rift on May 13, 9256 on May 22, nine days apart.
The disclosure credits are different — Leo Lin / DepthFirst for Rift, Mufeed VH / Winfunc Research for 9256 — but both sit in ngx_http_rewrite_module’s PCRE-capture expansion into the substitution string.

The natural read: post-Rift, another researcher audited the same area from a different angle and found another instance of the same bug class.
nginx has been alive for 18 years, and PCRE-capture expansion is a code path where pre-escape and post-escape sizes have always been easy to mis-account.
Now that Rift turned attention on ngx_http_rewrite_module, more audit work in that area is likely.

njs is a separate module, but it surfaced at F5 around the same time and got reported alongside the May 2026 nginx story.
Technically, though, it’s an independent CVE outside the Rift chain, so F5 advisories K000161019 (Rift) and K000161307 (njs) need to be tracked separately.


Honestly, having published a .htaccess generator myself, every time a “rewrite-substitution-too-long” class CVE drops I go check whether my generator’s output hits the trigger.
For now it doesn’t emit overlapping captures so it’s clear, but for anyone running an LLM-written rewrite directly to prod, “re-read the generated config via nginx -T” is a habit worth keeping for a while.

References