EST. 2026 — Notes from real server work SYSADMIN · SECURITY · AI · DIRECTADMIN Issue 01
THE SYSADMIN ALMANAC
Home / Security / nginx heap overflow spans 15 years—version checks aren't enough
Security

nginx heap overflow spans 15 years—version checks aren't enough

by Ton-Or ·16 Jul 2026·9 min read
nginx heap overflow spans 15 years—version checks aren't enough

Checking the version alone is not enough for this bug. You have to inspect the config to see whether it meets the conditions, and the actual conditions are considerably broader than most news reports describe. Check against narrow conditions and you get a false all-clear.

I saw the news about CVE-2026-42533 in nginx the other day, so I went through the machines I maintain. At first, I checked against what the news reports described. Everything passed, and I felt relieved for a while.

But when I read the write-up by the researcher who actually reported the bug, it turned out that the conditions for exposure were much broader than the news summaries suggested. If I had trusted that first check and closed the matter, I would have given myself a false all-clear, so I had to go back and check everything again.

I thought a lot of people might make the same mistake, so I am sharing what happened, along with the checks I actually used.

What the bug is

When nginx builds a string from variables in the config, it works in two passes. The first measures the length of the result and allocates that much memory. The second writes the actual data.

The problem is that both passes use capture values from the same regex, and if another regex runs between those two passes, it overwrites the original capture values.

Broken two-pass flowPass 1Measure original capture→Intervening regexOverwrites capture state→Pass 2Write using new capture→heap overflowData exceeds allocation

The result is a buffer allocated to fit one capture but filled with data from another whose length the attacker can control. Both the length and the contents of the overflow come directly from the request.

At a minimum, the worker process crashes and restarts = DoS. For more severe outcomes, F5 says remote code execution is possible if ASLR is disabled or can be bypassed. The researcher who reported it argues that the bug itself also provides an information leak that can bypass ASLR, meaning it goes beyond DoS. No one has been able to confirm this yet because the researcher has not released a PoC.

Which versions are affected

0.9.6-1.31.2Affected versions
15 yearsBack to 2011
1.31.3 / 1.30.4Fixed versions
13Trigger points in the code

The affected range is 0.9.6 through 1.31.2, going back to 2011, when map first supported regex. That means almost anyone who has not updated in a long time falls within this range. It is fixed in 1.31.3 (mainline), 1.30.4 (stable), and NGINX Plus 37.0.3.1.

The version tells only half the story

Being in this range does not automatically mean you are exposed, and being outside it does not mean you are safe if you have not updated. What actually decides it is whether that machine's config meets the conditions.

The trap: checking against news reports gives a false all-clear

Most news reports summarize the conditions as something like "a map that uses regex, with its variable concatenated into a string alongside capture $1 from an earlier regex."

After reading that, most people will grep for lines containing both $1 and a map variable. That is what I did on my first pass, and it found nothing.

What the news reports leave out

The capture and the map variable do not have to be on the same line, or even in the same directive — separate directives within the same location block are enough to trigger it. Grepping for lines containing both therefore returns "not found" even when the config fully meets the conditions.

An affected example, even though no line contains both at once:

location ~ "^/api/(.+)$" {
    proxy_set_header X-Path  "$1";          # capture ถูกวัดขนาดในรอบแรก
    proxy_set_header X-Check "$my_map_var"; # regex ของ map มาทับ capture state
    proxy_pass http://backend;
}
location ~ "^/api/(.+)$" {proxy_set_header X-Path "$1";proxy_set_header X-Check "$my_map_var";grep: only $1grep: only map varExposedBoth in thesame locationgrep for both on one line: 0 matches, but the config meets the conditionsCheck the whole block from nginx -T, not individual lines
The capture and map variable are on separate lines. Line-by-line grep sees only half the picture and returns an all-clear, even though having both in the same location already meets the conditions.

And it is not limited to map and proxy_set_header. The researcher found 13 reachable points in the code, covering almost all commonly used directives.

In the core script engine: return, add_header, proxy_redirect, proxy_method, auth_basic_user_file, proxy_pass, fastcgi_pass, root/alias, access_log, rewrite, set, and another set on the stream side.

In the modules: proxy_set_header, proxy_set_body, fastcgi_param, scgi_param, uwsgi_param, grpc_set_header, index.

Another very important point is that named captures are affected too. A capture like (?<name>...) does not write to the same place as $1, but takes another path. So if you only grep for $1 through $9, you still miss another path.

How to check your own machines

The first thing I need to stress is this: do not look only in /etc/nginx.

On machines running control panels like DirectAdmin/cPanel, or setups with lots of includes, the config nginx actually loads is spread across places you might not expect. On the machine I checked, 95 files were actually loaded, and 76 of them were outside /etc/nginx. Checking only that directory would miss almost all of the actual config.

Use nginx -T. It expands every level of includes:

nginx -T > /tmp/nf.txt
wc -l /tmp/nf.txt

Then the deciding question is whether the config references any captures. If there is not a single capture reference anywhere, nginx does not generate the opcode this bug needs. Even with dozens of maps, it cannot be exploited:

# อ้างถึง capture แบบตัวเลข
grep -cE '[$][1-9]' /tmp/nf.txt

# แบบใส่วงเล็บปีกกา
grep -cE '[$]\{[1-9]\}' /tmp/nf.txt

# named capture — อย่าลืมอันนี้เด็ดขาด
grep -cE '\(\?P?<[A-Za-z_]' /tmp/nf.txt

If all three return 0, the conditions are not currently met. Next, look at what can produce captures:

grep -cE '^\s*location\s+~\*?\s+.*\(' /tmp/nf.txt
grep -cE '^\s*server_name\s+.*~'      /tmp/nf.txt
grep -cE '^\s*rewrite\s+.*\('         /tmp/nf.txt
grep -cE '^\s*if\s*\(.*~.*\('         /tmp/nf.txt

Do not skip regex server_name entries. Hosting admins often write something like ~^(?<sub>.+)\.example\.com$ to handle wildcard subdomains, and that is a named capture.

Then check where each map is used:

grep -E '^\s*map\s+' /tmp/nf.txt | sed -E 's/\{.*//' | awk '{print $NF}' | sort -u

Once you have the variable names, check whether they are used in the same block as a capture reference. Again, inspect the whole block, not individual lines.

How to interpret the results

No captures anywhere

Not currently exploitable

No $1 and no named captures anywhere = the bug is unreachable, even with regex maps present. But you are safe because of the config, not because of a patch. If you change the config to add a capture, you need to check again.

Capture + regex map

Treat the conditions as met

If a referenced capture and a regex map are both active in the same request, with the capture referenced first = take action promptly. Do not sit around arguing about whether the order will line up exactly.

Mine fell on the left. There were locations with parentheses, but they were things like location ~ /(\.htaccess|\.env|\.git) { deny all; }, containing only deny all; and never using the capture to build a string. The parentheses do create a capture, but nothing references it, so no buffer goes through the measure-then-write process.

How to fix it

Update to 1.31.3 or 1.30.4. This is the only way to fully resolve it.

As for F5's recommended mitigation of changing regex maps to use named captures instead, I think caution is needed here. The researcher who reported the bug points out that named captures provide another path to the same overflow, one that F5's advisory does not mention. So it can reduce the risk, but it does not fully close the hole. Do not use it as a substitute for updating and assume you can sleep soundly.

If you cannot update right now

Some people are waiting for their control panel to make a new version available to build. While waiting, check the config as described above. If no captures are referenced anywhere, you have some breathing room, but keep this firmly in mind: no one should add captures to the config during this period without checking again.

Another thing to watch is control panel templates that no one has enabled yet. I found $1 in templates for WordPress FastCGI cache and dokuwiki, but no domains currently use them, so they do not appear in the config that is actually loaded. The day someone enables FastCGI cache for a customer, captures appear immediately. Keep that in mind.

Checklist

  1. Check the version first with nginx -v — if it is between 0.9.6 and 1.31.2, keep reading.
  2. Use only nginx -T. Do not check just /etc/nginx, because much of the actual config lives elsewhere.
  3. The deciding question is whether any captures are referenced — check $1, ${1}, and named captures (?<n>).
  4. Do not grep for lines containing both a capture and a map. Separate directives can still be affected. Check the whole block.
  5. It is not just map and proxy_set_header — there are 13 points, including return, add_header, rewrite, set, fastcgi_param, and index.
  6. Updating to 1.31.3 or 1.30.4 is the only complete fix. Switching to named captures does not fully close the hole.
  7. No captures today does not mean no captures forever — check again whenever you change the config.

My lesson from this is that when a major CVE comes out, I should not check only against news summaries. I should also read what the person who actually reported the bug wrote, because news reports simplify things for readability, and the details they leave out can be exactly what makes a check wrong. If I had stopped after my first pass this time, I would have missed it.

Feel free to turn the commands above into a script to check several machines at once. If I left anything out or made a typo, I will come back and correct it later.

Want us to set this up?

Get help with server setup, configuration, migration, or troubleshooting. Save time for the work that matters.

Get a quote

Hit something like this? Stuck somewhere?

Come trade notes on the LinuxThai board

→ Continue on the board

Comments 0

No comments yet — be the first