Skip to content
Attack Surface Management

CVE-2026-42533: NGINX map Directive Heap Buffer Overflow – Advisory + Nuclei Detection

xer0dayz · · 11 min read

On July 15, 2026, F5 patched CVE-2026-42533, a heap buffer overflow in NGINX reachable through the map directive. F5 scored it 9.2 Critical under CVSS 4.0. Twelve days later, on July 27, fully weaponized exploit code appeared on GitHub claiming ten-out-of-ten reliability against a current Ubuntu build. The vulnerable code path has existed since the map directive gained regular expression support in March 2011.

This advisory is three things at once. First, a plain-English explanation of the bug and, more importantly, of the version arithmetic – which has a wrinkle that will cause a lot of scanners to report false negatives across an entire release branch. Second, a ready-to-run Nuclei detection template with the version logic done correctly, plus a configuration audit template that answers the question version detection cannot. Third, how to find every affected NGINX instance across an attack surface with Sn1per.

CVE-2026-42533 at a glance

  • CVE: CVE-2026-42533 – heap-based buffer overflow (CWE-122) in the NGINX script engine, reached via the map directive.
  • Impact: out-of-bounds write plus an uninitialised-heap read. Chained, the read supplies the ASLR bypass the write needs, which is why public tooling claims pre-authentication RCE rather than the denial of service the vendor advisory leads with.
  • CVSS: 9.2 Critical under CVSS 4.0 (F5), 8.1 High under CVSS 3.1.
  • Affected: 0.9.6 through 1.31.2 – but see the version-window section, because that range is not contiguous in practice.
  • Fixed: 1.30.4 (stable) and 1.31.3 (mainline). NGINX Plus 37.0.3.1 or R36 P7.
  • Status: weaponized public proof-of-concept since July 27, 2026, with a documented ASLR bypass.
  • Age: introduced March 2011, disclosed 2026. Fifteen years of exposure.
  • Shipped alongside: CVE-2026-60005 (slice module memory disclosure) and CVE-2026-56434 (SSI use-after-free). One upgrade closes all three.

What is CVE-2026-42533?

NGINX evaluates complex string expressions in two passes. The LEN pass measures how many bytes the result needs so the right buffer can be allocated; the VALUE pass writes the bytes into it. Both passes read capture groups from the same mutable array, r->captures, and the whole scheme is correct only if that array holds the same contents during both walks.

A map directive whose pattern is a regular expression breaks that guarantee. When such a map executes between two references to a capture group, it silently rewrites the shared capture state. The LEN pass measured against one value; the VALUE pass writes a different one. The two disagree, and the copy runs off the end of the allocation.

What makes this more serious than a typical size-mismatch overflow is that the primitive runs in both directions, and the two directions compose into a complete exploit:

  • When the clobbering capture is larger than the original, the VALUE pass writes past the end of a buffer sized for the smaller one. That is an attacker-influenced out-of-bounds write.
  • When it is smaller, the buffer is oversized and its tail is never initialised. The response returns raw heap memory, leaking libc and heap pointers.

The leak supplies exactly what the write is missing. F5’s advisory is conservative, describing code execution as conditional on ASLR being disabled or bypassable. Researchers have pushed back on that framing with a specific argument: the bug provides its own ASLR bypass, because on a default Ubuntu build a single unauthenticated GET recovers the addresses a payload needs. That is the difference between a theoretical RCE and a practical one, and it is why the published exploit claims the reliability it does.

This is the same defect class as CVE-2026-42945, “NGINX Rift,” disclosed two months earlier. Same file, src/http/ngx_http_script.c, same two-pass contract violation, different directive as the entry point.

The version window trap: why 1.31.x is not safe

This is the section we would most like people to take away, because it is where automated checks go wrong.

The affected range is usually written as “0.9.6 through 1.31.2.” Read literally as a single interval, that is fine. Implemented literally as a single interval, it is also fine. The problem is what happens when someone reasons from the fix version instead, which is the natural thing to do: 1.30.4 is fixed, so surely anything above 1.30.4 is fixed.

It is not. NGINX maintains stable and mainline branches in parallel. The fix landed on stable as 1.30.4 and on mainline as 1.31.3. But mainline 1.31.0, 1.31.1 and 1.31.2 were all cut before that fix. So the vulnerable set has a hole in the middle:

Version range Status
0.9.6 – 1.30.3 Vulnerable
1.30.4 and later on the stable branch Fixed
1.31.0 – 1.31.2 Vulnerable again
1.31.3 and later Fixed

Any check written as a simple “is the version below 1.30.4” comparison will report every mainline 1.31.x host as patched, including the three releases that are not. That is a false negative across an entire branch, and it fails silently – the scan comes back clean and nobody investigates. Our template handles it with two disjoint matchers, which we show below.

Contrast this with NGINX Rift, where stable is fixed from 1.30.1 and mainline from 1.31.0. Everything above 1.30.0 is patched, so a single upper bound genuinely is correct there. Two closely related CVEs in the same file, two different shapes of version logic. Getting them backwards produces silent misses in both directions.

Am I affected? Quick manual checks

Locally, one command, read against the table above:

nginx -v

# nginx version: nginx/1.29.2   -> vulnerable
# nginx version: nginx/1.30.1   -> vulnerable (fixes Rift only)
# nginx version: nginx/1.31.1   -> vulnerable (mainline cut before the fix)
# nginx version: nginx/1.30.4   -> fixed
# nginx version: nginx/1.31.3   -> fixed

Remotely and unauthenticated, read the Server header, falling back to the footer of NGINX’s own default error page:

curl -sI https://target.example.com | grep -i '^server:'
curl -s https://target.example.com/does-not-exist-12345 | grep -i 'center>nginx'

Then check whether the configuration reaches the bug, since a regex-based map is the precondition:

grep -rnE '^\s*map\s+.*\{' /etc/nginx/
grep -rnE '^\s*~\*?.*\([^?]' /etc/nginx/

As always, absence of a banner is not evidence of patching. server_tokens off suppresses the version, and edges routinely rewrite the header. Treat a silent host as unknown.

CVE-2026-42533 Nuclei detection template

Below is the full template. It is non-destructive: one ordinary GET, version comparison only, no overflow payload, no contact with the vulnerable code path. Note the two separate matchers, which is the whole point of the version-window section above.

id: CVE-2026-42533

info:
  name: NGINX - map Directive Regex Heap Buffer Overflow (CVE-2026-42533)
  author: xer0dayz
  severity: high
  description: |
    Heap-based buffer overflow (CWE-122) in the NGINX script engine. A map
    directive with a regex pattern, executing between two references to a capture
    group, rewrites the shared r->captures array so the LEN and VALUE passes
    disagree on buffer size.

    NON-DESTRUCTIVE and PASSIVE. One ordinary GET, version comparison only.

    Treat a hit as a patch-gap finding, NOT a proven exploitable RCE. Exploitation
    is config-gated on a regex-based map whose output is referenced after an
    earlier unnamed capture.
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
    cvss-score: 8.1
    cve-id: CVE-2026-42533
    cwe-id: CWE-122
  metadata:
    verified: false
    max-request: 2
    vendor: f5
    product: nginx
    patched-versions: "1.30.4, 1.31.3"
  tags: cve,cve2026,nginx,f5,overflow,rce,patch-gap,passive

http:
  - method: GET
    path:
      - "{{BaseURL}}"
      - "{{BaseURL}}/{{randstr}}"

    stop-at-first-match: true
    redirects: false

    # Two disjoint vulnerable windows, because the fix landed on the stable and
    # mainline branches at different points. Stable is fixed from 1.30.4, so
    # everything from 0.9.6 up to 1.30.3 is affected. Mainline 1.31.0 through
    # 1.31.2 shipped before the fix and is affected again; 1.31.3 closes it.
    matchers-condition: or
    matchers:
      - type: dsl
        name: nginx-0.9.6-to-1.30.3
        dsl:
          - "version != '' && compare_versions(version, '>= 0.9.6', '< 1.30.4')"

      - type: dsl
        name: nginx-mainline-1.31.0-to-1.31.2
        dsl:
          - "version != '' && compare_versions(version, '>= 1.31.0', '< 1.31.3')"

    extractors:
      - type: regex
        name: version
        part: response
        group: 1
        internal: true
        regex:
          - '(?:[Ss]erver:\s*nginx/|<center>nginx/)([0-9]+\.[0-9]+\.[0-9]+)'

      - type: regex
        name: nginx_version
        part: response
        group: 1
        regex:
          - '(?:[Ss]erver:\s*nginx/|<center>nginx/)([0-9]+\.[0-9]+\.[0-9]+)'
nuclei -t CVE-2026-42533.yaml -l targets.txt

We verified both windows against a twelve-version mock matrix, checking every boundary: 1.30.3 flags, 1.30.4 does not, 1.31.0 flags again, 1.31.3 does not. All twelve boundary cases pass.

The second template: auditing the configuration

A version match is a patch-gap finding, not proof of exploitability, because this bug is config-gated. It only surfaces when a regex-based map directive’s output variable is referenced in a string expression after an unnamed capture from an earlier regex match. An attacker cannot create that arrangement remotely.

So the companion template reads the configuration and looks for the ingredients directly:

file:
  - extensions:
      - all

    matchers:
      # All three ingredients must be present in the same file: a map block, a
      # regex-keyed entry that uses an UNNAMED capture, and capture usage.
      - type: regex
        name: cve-2026-42533-map-regex-with-unnamed-capture
        condition: and
        regex:
          - '(?im)^\s*map\s+[^{\n]*\{'
          - '(?m)^\s*~\*?[^\n;]*\([^?]'
          - '\$[1-9]'

The middle pattern deserves a note, because it is where the first draft of this template was wrong. Our initial version required a map block, a regex-keyed entry, and $1 anywhere in the file. That fired on a safe configuration whose map used named captures – (?<name>...) – but which happened to contain an unrelated $1 in a rewrite elsewhere in the same file. Named captures are not vulnerable, so that was a false positive on exactly the configuration that had done the right thing.

The fix is to require the unnamed capture to appear inside the map entry itself. Expressing “an open parenthesis not followed by a question mark” is normally a job for negative lookahead, which Go’s RE2 engine does not support. The workaround is \([^?] – an open parenthesis followed by any character that is not a question mark. That clears (?<name>, (?P<name> and (?:, all of which are safe, while still catching the vulnerable (.*) form.

Run it against the config tree. The -file flag is required, or Nuclei silently skips file-protocol templates:

nuclei -file -t nginx-script-engine-config-audit.yaml -target /etc/nginx/

Two honest limitations. The matcher is file-scoped, so a split configuration that puts the map block in conf.d/maps.conf and the capture usage in sites-enabled/site will not match either file on its own. And a clean audit is never a reason to leave an unpatched binary running, because any future config change can introduce the pattern.

Finding affected NGINX across your attack surface with Sn1per

Checking one host is easy. The real problem is the host you forgot: the staging environment behind a stale DNS record, the appliance that embeds NGINX as a component, the ingress controller from a proof of concept nobody decommissioned. Those hosts are exactly the ones that sit on old builds forever, because no patch process knows they exist.

That is attack surface discovery, and it is what Sn1per does. Discovery first, detection second:

sniper -t example.com -m recon
sniper -t example.com -m web

Both templates ship in Sn1per’s curated set, so affected hosts surface automatically during enumeration. The check is passive, so it is safe to run against production on a schedule.

The output that matters is not a single verdict but a version inventory across the estate – which is the only way to catch the hosts sitting on 1.31.1 that a naive “below 1.30.4” check would have cleared.

Which Sn1per edition fits

  • Sn1per Community Edition – free and open source. Run the templates by hand against hosts you already know about.
  • Sn1per Professional 2026 – web UI, scheduled scans and reporting. Turns a one-off check into continuous coverage.
  • Sn1per Enterprise – multi-workspace attack surface management with continuous monitoring and alerting, for estates where the NGINX inventory is genuinely unknown.

Remediation and mitigation

  1. Upgrade to 1.30.4 (stable) or 1.31.3 (mainline). These close CVE-2026-42533, CVE-2026-60005 and CVE-2026-56434 together, and also carry the earlier CVE-2026-42945 fix. NGINX Plus users want 37.0.3.1 or R36 P7.
  2. Do not stop at 1.30.1. That release fixes only NGINX Rift and leaves this bug open. It is the most common partial-patch mistake we expect to see.
  3. Patch downstream products. NGINX Ingress Controller, Gateway Fabric, App Protect WAF and Instance Manager ship the same script engine.
  4. Interim mitigation only: replace unnamed regex captures with named captures in map directives, and reference them only inside the block holding the regex match. Note that CVE-2026-56434, shipped in the same release, has no documented workaround, so mitigation cannot substitute for the upgrade.
  5. Do not rely on server_tokens off. It hides the version from casual fingerprinting. It does not remediate anything.

Frequently asked questions

Is nginx 1.31.1 affected by CVE-2026-42533?
Yes. This catches people out. The fix landed on stable as 1.30.4 and on mainline as 1.31.3, and mainline 1.31.0 through 1.31.2 were cut before it. A version higher than 1.30.4 is not automatically patched, because it may be on the other branch.

Does upgrading to 1.30.1 fix CVE-2026-42533?
No. 1.30.1 fixes CVE-2026-42945 (NGINX Rift) only. CVE-2026-42533 was fixed in 1.30.4, alongside CVE-2026-60005 and CVE-2026-56434.

Is there a public exploit for CVE-2026-42533?
Yes. Weaponized proof-of-concept code has been public since July 27, 2026, including a chained heap leak that defeats ASLR. We do not link or reproduce weaponized code here; the templates in this advisory are detection-only.

Can I safely test whether my server is vulnerable?
Not by exploiting it. Triggering the bug corrupts a worker heap and crashes the worker. Use passive version detection plus the configuration audit. Never attempt active confirmation against a bug bounty target or an asset you do not own.

Does a vulnerable version mean I am exploitable?
Not necessarily. Exploitation requires a regex-based map whose output is referenced after an earlier unnamed capture. Distributions also backport fixes without changing the version string, and the banner you see may belong to a CDN edge rather than the origin. Treat a version hit as a patch gap to investigate, not a confirmed RCE.

Written by

xer0dayz

Founder of XeroSecurity.

Try it free

See your attack surface like a pentester would.

Sn1per finds, ranks, and exploits real vulnerabilities autonomously — the same way attackers do.