
CVE-2026-42533: Pre-Auth-Heap-Overflow in nginx und Informationsleck durch PCRE-Capture-Clobbering in der Map/Script-Engine, verkettet zu RCE.
Pre-Auth-Heap-Pufferüberlauf und Heap-Information-Leak in nginx, verursacht durch fehlendes Speichern/Wiederherstellen des PCRE-Capture-Zustands über die beiden Durchläufe der Script-Engine. Eine zwischen zwei Capture-Referenzen ausgewertete Regex-map-Variable überschreibt r->captures, sodass der LEN-Durchlauf und der VALUE-Durchlauf unterschiedlicher Meinung darüber sind, wie groß ein Capture ist. Der Puffer wird für ein Capture dimensioniert und mit einem anderen gefüllt. Ein größeres Überschreiben ergibt einen Heap-Overflow mit vom Angreifer kontrolliertem Inhalt und Länge. Ein kleineres Überschreiben ergibt einen überdimensionierten Puffer, dessen nicht initialisiertes Ende an den Client zurückgegeben wird und libc- und Heap-Zeiger leakt.
Die beiden Primitiven lassen sich zu zuverlässiger Pre-Auth-Remote-Codeausführung verketten. Der Leak überwindet ASLR in einem einzigen GET, sodass für den Overflow ASLR nicht deaktiviert sein muss.
Writeup: https://cyberstan.co.uk/nginx-rce/ Advisory: F5 K000162097 Reporter: Stan Shaw (cyberstan)
nginx 0.9.6 bis 1.30.3 (stable) und 1.31.2 (mainline), erreichbar, seit die map-Direktive 2011 Regex-Unterstützung erhielt. Betroffen sind sowohl die http- als auch die stream-Module. Etwa 50 Direktiven an 13 Aufrufstellen, plus ein zweiter Pfad über benannte Captures (r->variables[]). Behoben in 1.30.4 und 1.31.3.
CVE-2026-42533-PoC/
├── exploits/ exploit and validator scripts
│ ├── poc.py numbered-capture proxy_method chain (crash/leak/rce/rce-det)
│ ├── calibrate.py find PL_OFF / HEAP_PAGE_OFF for --rce-det on your build
│ ├── leak_multi.py info leak on the return and set sinks
│ ├── test_all_sites.py ASan validator, all 13 overflow sites
│ └── named_capture_poc.py named-capture r->variables[] variant
├── configs/ nginx configs the exploits run against
│ ├── nginx_poc.conf crash / leak / rce
│ └── nginx_det.conf deterministic rce-det
├── docs/
│ └── EXPLOITATION.md full exploitation writeup
└── README.md
Führen Sie jeden unten stehenden Befehl vom Repository-Root aus, damit exploits/, configs/ und der Build-Baum ../nginx-1.30.1 alle aufgelöst werden.
exploits/poc.py ist der Hauptexploit (nummeriertes Capture, proxy_method-Senke). Seine Modi:
| Modus | Zweck |
|---|---|
poc.py --crash | Löst den Heap-Overflow aus; bei einem ASan-Build werden Schreibgröße und Stack bei ngx_http_script_copy_capture_code ausgegeben. |
poc.py --leak | Info-Leak: Gibt die libc- und Heap-Zeiger aus dem überdimensionierten Antwortbody aus. |
poc.py --rce | Vollständige Pre-Auth-RCE. Allgemeiner Einzelschuss (configs/nginx_poc.conf), ~66 % pro Versuch, bei Fehlschlag erneut ausführen. |
poc.py --rce-det | Vollständige Pre-Auth-RCE, deterministisch gegen die kontrollierte configs/nginx_det.conf. |
Eigenständige Skripte, die den Rest der Angriffsfläche des Bugs abdecken:
| Skript | Zweck |
|---|---|
exploits/leak_multi.py | Info-Leak über zwei weitere Evaluatoren (return, set), jeder libc+Heap-Zeiger gegen /proc/<pid>/maps bestätigt. Standardkonfiguration. |
exploits/test_all_sites.py | AddressSanitizer-Validator, der alle 13 Overflow-Aufrufstellen (http + stream) auslöst. |
exploits/named_capture_poc.py | Die Named-Capture-Variante (?P<name>...) über r->variables[] / copy_var_code, eine zweite Grundursache. |
Die Konfigurationen liegen in configs/: nginx_poc.conf (crash/leak/rce), nginx_det.conf (rce-det). Der vollständige Writeup ist docs/EXPLOITATION.md.
Linux, gcc, python3 und der nginx-1.30.1-Quellcode. Entwickelt und getestet auf Ubuntu 24.04.4, glibc 2.39, PCRE2 10.42, Python 3.12, vollständigem ASLR.
Zwei Builds. Ein sauberer Build für den Leak und die RCE, damit die Heap-Rückstände real sind. Ein AddressSanitizer-Build für den Crash und die Site-Validatoren, damit der Overflow mit exakter Schreibgröße und Stack gemeldet wird.
tar xf nginx-1.30.1.tar.gz
cd nginx-1.30.1
# clean build -> objs.dbg/nginx (leak, rce)
./configure --with-pcre --with-http_ssl_module --with-debug --builddir=objs.dbg
make -j"$(nproc)"
# ASan build with all the modules the site validator needs -> objs/nginx
./configure --with-pcre --with-http_ssl_module --with-http_v2_module \
--with-stream --with-stream_ssl_preread_module --with-stream_ssl_module \
--with-debug \
--with-cc-opt='-g -O0 -fsanitize=address -fno-omit-frame-pointer' \
--with-ld-opt=-fsanitize=address --builddir=objs
make -j"$(nproc)"
exploits/poc.py spricht mit einem laufenden nginx auf 127.0.0.1:8950. Starten Sie es in einem Terminal mit configs/nginx_poc.conf und führen Sie dann den gewünschten Modus in einem anderen aus. Die anderen drei Skripte starten und stoppen ihr eigenes nginx und benötigen daher nur NGINX_BIN.
mkdir -p run/logs
../nginx-1.30.1/objs/nginx -p run -c "$PWD/configs/nginx_poc.conf" # ASan build, foreground
python3 exploits/poc.py --crash
Erwartet: ein heap-buffer-overflow, WRITE of size 200 in ngx_http_script_copy_capture_code bei ngx_http_script.c:1404, aufgerufen aus ngx_http_complex_value in ngx_http_proxy_create_request.
../nginx-1.30.1/objs.dbg/nginx -p run -c "$PWD/configs/nginx_poc.conf" # clean build
python3 exploits/poc.py --leak
Erwartet: ein 8161-Byte-Body mit 2 geschriebenen Bytes und dem Rest als Heap-Rückstand. Ein libc-Zeiger bei Offset 0x08 und ein Heap-Zeiger bei 0x10.
../nginx-1.30.1/objs.dbg/nginx -p run -c "$PWD/configs/nginx_poc.conf" # clean build
python3 exploits/poc.py --rce # writes /tmp/PWNED via system()
Erwartet: ein Leak, etwa 40 Spray-Verbindungen, ein Overflow-Trigger, dann /tmp/PWNED mit der Ausgabe von id. Dies ist ein Einzelschuss und trifft im Dev-Build etwa zwei Drittel der Fälle; bei einem Fehlschlag stürzt der Worker ab und Sie führen es erneut aus. Siehe Abschnitt „Reliability" in docs/EXPLOITATION.md.
Gegen eine kontrollierte Konfiguration ist derselbe Bug ein deterministischer Einzelschuss. Er ermittelt die absolute Heap-Basis aus dem Leak in einer Zeile (heap_base = (leaked_ptr & ~0xfff) - 0x22000), platziert ein einziges gefälschtes Pool-Cleanup in einer gehaltenen Verbindung an einer bekannten Adresse und richtet das Cleanup des Opfer-Pools darauf statt auf den transienten Trigger-Body, den nginx vor dem Teardown freigibt.
mkdir -p run/logs
../nginx-1.30.1/objs.dbg/nginx -p run -c "$PWD/configs/nginx_det.conf" # controlled config
python3 exploits/poc.py --rce-det
nginx_det.conf ist eine Labor-Konfiguration (einzelner Worker, feste Puffer), deren Heap-Layout reproduzierbar ist, was die Offsets in poc.py (PL_OFF, HEAP_PAGE_OFF) gültig macht. PL_OFF ist der Offset von der Heap-Basis, an dem der gehaltene POST /b/-Cleanup-Body landet; er hängt von der exakten Allokationssequenz ab und verschiebt sich daher zwischen Builds, glibc-Versionen und Konfigurationen. Wenn --rce-det Recalibrate meldet, lesen Sie die korrekten Werte mit calibrate.py von einem laufenden Worker ab:
NGINX_BIN=../nginx-1.30.1/objs.dbg/nginx python3 exploits/calibrate.py
# prints e.g. set PL_OFF = 0x14426 , then edit it in exploits/poc.py
Der einzige andere Nicht-Erfolg ist eine ASLR-Ziehung, die ein 0x0a in die Cleanup-Adresse oder den Overflow-Body legt, was die Map-Regex nicht transportieren kann; das Tool meldet es und Sie führen es erneut aus. Ein Standard-Deployment ist nicht so vorhersagbar, verwenden Sie dort also --rce. Siehe „A deterministic build" in docs/EXPLOITATION.md.
NGINX_BIN=../nginx-1.30.1/objs/nginx python3 exploits/test_all_sites.py # ASan build
NGINX_BIN=../nginx-1.30.1/objs/nginx python3 exploits/test_all_sites.py 1 7 12 # a subset
NGINX_BIN=../nginx-1.30.1/objs/nginx python3 exploits/named_capture_poc.py # ASan build
NGINX_BIN=../nginx-1.30.1/objs.dbg/nginx python3 exploits/leak_multi.py # clean build
Erwartet: return und set leaken jeweils einen libc- und einen Heap-Zeiger, beide gegen die /proc/<pid>/maps des Workers bestätigt.
Zwei Dinge bereiten den meisten Menschen Probleme: die Verwendung des falschen Builds (ASan vs. sauber) und die --rce-det-Offsets, die nicht zur eigenen Umgebung passen. Beides wird unten behandelt.
| Symptom | Ursache | Lösung |
|---|---|---|
--leak zeigt keine Zeiger, oder --rce / --rce-det trifft nie | Sie verwenden den ASan-Build; AddressSanitizer vergiftet freigegebenen Speicher, daher enthalten die Rückstände keine echten Zeiger | Verwenden Sie den sauberen objs.dbg-Build für --leak, --rce, --rce-det und leak_multi.py. Der ASan-objs-Build ist nur für --crash, test_all_sites.py und named_capture_poc.py gedacht. |
--rce trifft nur etwa 2 von 3 Versuchen | Einzelschuss gegen eine allgemeine Konfiguration; der Ort des freigegebenen Trigger-Bodys variiert | Erwartet. Bei einem Fehlschlag stürzt der Worker ab und startet neu, führen Sie es also einfach erneut aus. Für einen deterministischen Einzelschuss verwenden Sie --rce-det. |
--rce-det gibt bei jedem Lauf No RCE. Recalibrate PL_OFF/HEAP_PAGE_OFF aus | PL_OFF ist spezifisch für Ihren Build, Ihre glibc, Ihre Konfiguration und die Länge des nginx--p-Präfixpfads. Der mitgelieferte Wert gilt für den Dev-Build dieses Repos, gestartet mit -p run. | Führen Sie exploits/calibrate.py aus, fügen Sie das ausgegebene PL_OFF in exploits/poc.py ein und starten Sie nginx mit dem gleichen -p-Präfix, das Sie calibrate.py gegeben haben (das README verwendet -p run). |
--rce-det gibt gelegentlich 0x0a (regex-hostile) ... retry aus | Eine ASLR-Ziehung hat ein 0x0a-Byte (Zeilenvorschub) in eine Adresse gelegt, das die map-Regex nicht transportieren kann | Kein Fehler. Erneut ausführen; die nächste Ziehung vermeidet es fast immer. |
nginx oder calibrate.py: bind() to 127.0.0.1:8950 failed (Address already in use) | Ein früheres nginx hält den Port weiterhin | pkill -x nginx, eine Sekunde warten, erneut versuchen. Lassen Sie nur ein nginx auf 8950 laufen. |
--rce ermittelt zur Laufzeit die libc-Basis und einen Heap-Zeiger aus dem Leak und kodiert daher keine Adresse fest. Es kodiert jedoch Offsets fest, die spezifisch für den Build und die libc sind, auf denen es entwickelt wurde:
LIBC_LEAK_OFFSET libc base to the leaked arena pointer
SYSTEM_OFFSET libc base to system()
BODY_DELTA_* leaked heap pointer to the overflow body buffer
POOL_OFF_FROM_BUF, D_LAST_OFF, D_END_OFF, LOG_OFF forged pool geometry
Auf einer anderen Distribution, glibc oder einem anderen nginx-Build müssen diese neu kalibriert werden. Lesen Sie den echten system()-Offset mit readelf -sW /lib/x86_64-linux-gnu/libc.so.6 | grep '\bsystem\b' und die Pool-Offsets aus dem ngx_pool_t des Ziels. Die Modi --crash und --leak enthalten keine solchen Offsets und reproduzieren auf jedem Build einer betroffenen Version.
Der Leak benötigt keine spezielle Abstimmung. Er funktioniert mit einer Standard-events {}-Konfiguration, deren standardmäßiges worker_connections (512) die Arena so dimensioniert, dass der freigegebene Request-Chunk in einem glibc-Bin landet, der noch Arena- und Heap-Zeiger enthält, die der überdimensionierte Leak-Puffer wiederverwendet. Nur ein ungewöhnlich niedriges worker_connections (unter ~256) entgeht ihm; Produktionswerte (512-1024) leaken alle.
Ein statischer Konfigurationsscanner, der das verwundbare Muster kennzeichnet, ohne etwas zu exploitieren, befindet sich unter https://github.com/0xCyberstan/CVE-2026-42533-Config-Scanner.
Dies richtet sich gegen eine gepatchte, öffentlich offengelegte Schwachstelle. Es existiert für Verteidiger, die die Gefährdung überprüfen, und für die Reproduktion in der Forschung. Führen Sie es nur gegen nginx aus, das Ihnen gehört oder für dessen Test Sie ausdrücklich autorisiert sind. Aktualisieren Sie auf 1.30.4 oder 1.31.3.
Ein eigenständiges Skript gibt nginx not found aus | NGINX_BIN nicht gesetzt oder zeigt auf den falschen Build | Setzen Sie NGINX_BIN (sauberer Build für leak_multi.py, ASan-Build für die Validatoren). |
No such file für eine Konfiguration, ein Skript oder die nginx-Binärdatei | Sie befinden sich nicht im Repository-Root | Wechseln Sie zuerst per cd in den Repository-Root; jeder Befehl setzt ihn voraus (exploits/..., configs/..., ../nginx-1.30.1/...). |
| Der Leak funktioniert mit einer Konfiguration, aber nicht mit einer anderen | worker_connections unter ~256 verkleinert die Arena, sodass der freigegebene Chunk keine Zeiger enthält | Verwenden Sie ein normales worker_connections (512 bis 1024). Produktionswerte leaken alle. |