
CVE-2026-42533: overflow dell'heap pre-autenticazione e fuga di informazioni in nginx causati dalla sovrascrittura delle catture PCRE nel motore map/script, concatenati per ottenere RCE.
Overflow del buffer heap e perdita di informazioni dall'heap pre-autenticazione in nginx, causati dalla mancata salvataggio/ripristino dello stato delle catture PCRE tra le due passate del motore degli script. Una variabile map basata su regex valutata tra due riferimenti di cattura sovrascrive r->captures, quindi la passata LEN e la passata VALUE non concordano sulla dimensione di una cattura. Il buffer viene dimensionato per una cattura e riempito con un'altra. Una sovrascrittura più grande produce un overflow dell'heap con contenuto e lunghezza controllati dall'attaccante. Una sovrascrittura più piccola produce un buffer sovradimensionato la cui coda non inizializzata viene restituita al client, divulgando puntatori libc e dell'heap.
Le due primitive si concatenano in un'esecuzione remota di codice pre-autenticazione affidabile. La perdita di informazioni aggira ASLR in una singola GET, quindi l'overflow non richiede ASLR disabilitato.
Analisi tecnica: https://cyberstan.co.uk/nginx-rce/ Avviso di sicurezza: F5 K000162097 Segnalatore: Stan Shaw (cyberstan)
nginx 0.9.6 fino a 1.30.3 (stable) e 1.31.2 (mainline), raggiungibile da quando la direttiva map ha ottenuto il supporto regex nel 2011. Sia i moduli http che stream. Circa 50 direttive in 13 punti di chiamata, più un secondo percorso tramite catture nominate (r->variables[]). Corretto in 1.30.4 e 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
Esegui ogni comando qui sotto dalla radice del repository, così che exploits/, configs/ e l'albero di build ../nginx-1.30.1 vengano risolti.
exploits/poc.py è l'exploit principale (cattura numerata, sink proxy_method). Le sue modalità:
| Modalità | Scopo |
|---|---|
poc.py --crash | Scatena l'overflow dell'heap; su una build ASan stampa la dimensione della scrittura e lo stack in ngx_http_script_copy_capture_code. |
poc.py --leak | Perdita di informazioni: estrae i puntatori libc e dell'heap dal corpo della risposta sovradimensionato. |
poc.py --rce | RCE pre-autenticazione completa. Singolo colpo generale (configs/nginx_poc.conf), ~66% a tentativo, ripeti in caso di errore. |
poc.py --rce-det | RCE pre-autenticazione completa, deterministica contro il configs/nginx_det.conf controllato. |
Script autonomi che coprono il resto della superficie del bug:
| Script | Scopo |
|---|---|
exploits/leak_multi.py | Perdita di informazioni tramite altri due valutatori (return, set), ogni puntatore libc+heap confermato contro /proc/<pid>/maps. Configurazione standard. |
exploits/test_all_sites.py | Validator AddressSanitizer che innesca tutti i 13 punti di chiamata dell'overflow (http + stream). |
exploits/named_capture_poc.py | La variante a cattura nominata (?P<name>...) tramite r->variables[] / copy_var_code, una seconda causa principale. |
Le configurazioni si trovano in configs/: nginx_poc.conf (crash/leak/rce), nginx_det.conf (rce-det). La documentazione completa è docs/EXPLOITATION.md.
Linux, gcc, python3 e il sorgente di nginx 1.30.1. Sviluppato e testato su Ubuntu 24.04.4, glibc 2.39, PCRE2 10.42, python 3.12, ASLR completo.
Due build. Una build pulita per la perdita di informazioni e la RCE, così il residuo dell'heap è reale. Una build AddressSanitizer per il crash e i validator dei punti di chiamata, così l'overflow viene segnalato con dimensione di scrittura e stack esatti.
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 comunica con un nginx in esecuzione su 127.0.0.1:8950. Avvialo in un terminale con configs/nginx_poc.conf, poi esegui la modalità che vuoi in un altro. Gli altri tre script avviano e arrestano un proprio nginx, quindi hanno bisogno solo di 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
Atteso: un heap-buffer-overflow, WRITE of size 200 in ngx_http_script_copy_capture_code a ngx_http_script.c:1404, chiamato da 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
Atteso: un corpo di 8161 byte con 2 byte scritti e il resto residuo dell'heap. Un puntatore libc all'offset 0x08 e un puntatore dell'heap a 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()
Atteso: una perdita di informazioni, circa 40 connessioni di spray, un trigger di overflow, poi /tmp/PWNED contenente l'output di id. È un singolo colpo e va a segno circa due volte su tre sulla build di sviluppo; in caso di errore il worker crasha e lo riesegui. Vedi la sezione "Reliability" di docs/EXPLOITATION.md.
Con una configurazione controllata lo stesso bug diventa un singolo colpo deterministico. Recupera la base assoluta dell'heap dalla perdita di informazioni in una riga (heap_base = (leaked_ptr & ~0xfff) - 0x22000), colloca una singola cleanup di pool contraffatta in una connessione trattenuta a un indirizzo noto e punta la cleanup del pool vittima su di essa invece che sul corpo trigger transitorio, che nginx libera prima del teardown.
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 è una configurazione da laboratorio (worker singolo, buffer fissi) il cui layout dell'heap è riproducibile, ed è ciò che rende validi gli offset in poc.py (PL_OFF, HEAP_PAGE_OFF). PL_OFF è l'offset dalla base dell'heap in cui atterra il corpo della cleanup POST /b/ trattenuta; dipende dalla sequenza esatta delle allocazioni, quindi varia tra build, versioni di glibc e configurazioni. Se --rce-det riporta Recalibrate, leggi i valori corretti da un worker attivo con calibrate.py:
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
L'unico altro esito non vincente è un'estrazione ASLR che inserisce un 0x0a nell'indirizzo della cleanup o nel corpo dell'overflow, un byte che la regex della direttiva map non può trasportare; lo strumento lo segnala e si riesegue. Una distribuzione standard non è altrettanto prevedibile, quindi lì usa --rce. Vedi "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
Atteso: return e set perdono ciascuno un puntatore libc e uno dell'heap, entrambi confermati contro /proc/<pid>/maps del worker.
Due cose mettono in difficoltà più di tutte: usare la build sbagliata (ASan vs pulita) e offset di --rce-det che non corrispondono al proprio ambiente. Entrambe sono trattate di seguito.
| Sintomo | Perché | Cosa fare |
|---|---|---|
--leak non mostra puntatori, oppure --rce / --rce-det non vanno mai a segno | Sei sulla build ASan; AddressSanitizer avvelena la memoria liberata, quindi il residuo non contiene puntatori reali | Usa la build pulita objs.dbg per --leak, --rce, --rce-det e leak_multi.py. La build ASan objs è solo per --crash, test_all_sites.py e named_capture_poc.py. |
--rce va a segno solo circa 2 volte su 3 | Singolo colpo contro una configurazione generale; la posizione del corpo trigger liberato varia | Previsto. In caso di errore il worker crasha e si rigenera, quindi basta rieseguire. Per un singolo colpo deterministico usa --rce-det. |
--rce-det stampa No RCE. Recalibrate PL_OFF/HEAP_PAGE_OFF a ogni esecuzione | PL_OFF è specifico per la tua build, glibc, configurazione e lunghezza del prefisso -p di nginx. Il valore incluso è per la build di sviluppo di questo repo avviata con -p run. | Esegui exploits/calibrate.py, incolla il PL_OFF che stampa in exploits/poc.py e avvia nginx con lo stesso prefisso -p che hai passato a calibrate.py (il README usa -p run). |
--rce-det occasionalmente stampa 0x0a (regex-hostile) ... retry | Un'estrazione ASLR ha messo un byte 0x0a (newline) in un indirizzo, un byte che la regex della direttiva map non può trasportare | Non è un errore. Riesegui; l'estrazione successiva lo evita quasi sempre. |
nginx o calibrate.py: bind() to 127.0.0.1:8950 failed (Address already in use) | Un nginx precedente occupa ancora la porta | pkill -x nginx, attendi un secondo, riprova. Tieni un solo nginx sulla 8950. |
Uno script autonomo stampa nginx not found |
--rce recupera la base libc e un puntatore dell'heap a runtime dalla perdita di informazioni, quindi non codifica alcun indirizzo. Codifica però alcuni offset specifici della build e della libc su cui è stato sviluppato:
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
Su una distro, glibc o build di nginx diversa questi offset vanno ricalibrati. Leggi il vero offset di system() con readelf -sW /lib/x86_64-linux-gnu/libc.so.6 | grep '\bsystem\b' e leggi gli offset del pool dalla ngx_pool_t del target. Le modalità --crash e --leak non includono tali offset e si riproducono su qualsiasi build di una versione affetta.
La perdita di informazioni non richiede regolazioni particolari. Funziona su una configurazione standard events {}, il cui worker_connections predefinito (512) dimensiona l'arena in modo che il chunk di richiesta liberato finisca in un bin di glibc che contiene ancora puntatori all'arena e all'heap, riutilizzati dal buffer di perdita sovradimensionato. Solo un worker_connections insolitamente basso (sotto ~256) la evita; tutti i valori di produzione (512-1024) perdono informazioni.
Uno scanner statico di configurazioni che segnala il pattern vulnerabile senza sfruttare nulla è disponibile su https://github.com/0xCyberstan/CVE-2026-42533-Config-Scanner.
Questo strumento riguarda una vulnerabilità corretta e divulgata pubblicamente. Esiste per consentire ai difensori di verificare l'esposizione e per la riproduzione in ambito di ricerca. Eseguilo solo contro nginx di tua proprietà o per cui hai esplicita autorizzazione al test. Aggiorna a 1.30.4 o 1.31.3.
NGINX_BIN non impostato o punta alla build sbagliata |
Imposta NGINX_BIN (build pulita per leak_multi.py, build ASan per i validator). |
No such file per una configurazione, uno script o il binario di nginx | Non sei nella radice del repository | Fai cd nella radice del repository prima; ogni comando la presuppone (exploits/..., configs/..., ../nginx-1.30.1/...). |
| La perdita di informazioni funziona su una configurazione ma non su un'altra | worker_connections sotto ~256 riduce l'arena, quindi il chunk liberato non contiene puntatori | Usa un worker_connections normale (da 512 a 1024). Tutti i valori di produzione perdono informazioni. |