← Voltar ao blog
// Post

"php2shell — RCE pré-autenticação no PHP mod_php"

Duas vulnerabilidades críticas foram divulgadas recentemente no PHP, afetando servidores Apache rodando mod_php: CVE-2026-109618 e CVE-2026-109619. Quando encadeadas, permitem execução remota de código sem autenticação — sem credenciais, sem bugs na aplicação, sem acesso a /proc ou GDB. Apenas requisições HTTP contra o próprio runtime do PHP.

Na DeepLook Labs, dedicamos tempo analisando ambas as vulnerabilidades, entendendo a cadeia de exploração e construindo um exploit funcional que alcança RCE em condições controladas. Neste post compartilhamos uma visão geral sobre o que essas CVEs representam e o que conseguimos confirmar.

As Vulnerabilidades

CVE-2026-109618 — Bucket stale na HashTable por OOM

Essa vive em _zend_hash_str_add_or_update_i dentro do Zend Engine. Quando uma condição de Out-of-Memory ocorre durante o processamento de uma requisição HTTP, a HashTable pode deixar buckets stale — ponteiros para memória já liberada. Na próxima requisição tratada pelo mesmo worker prefork do Apache, esses ponteiros são reutilizados. Use-After-Free clássico.

CVE-2026-109619 — Globais de sessão pendentes

Essa mira o mecanismo de upload progress de sessão em ext/session. Quando OOM acontece antes de PG(modules_activated)=1 — durante o parsing de multipart/form-data com session upload progress habilitado — a função de shutdown do módulo (zend_deactivate_modules()) é pulada inteiramente. O RSHUTDOWN da sessão nunca executa, deixando PS(id) e PS(http_session_vars) apontando para memória per-request que já foi liberada.

A próxima requisição que chega no mesmo worker acessa esses ponteiros pendentes. UAF controlável.

O que confirmamos

Após analisar a cadeia de vulnerabilidades descrita pelos pesquisadores originais, desenvolvemos um exploit funcional que alcança RCE em três estágios:

  1. Heap Leak — disparar o OOM para deixar PS(id) pendente, depois fazer spray de campos de formulário para reclamar a memória e vazar ponteiros do heap via Set-Cookie. Os ponteiros alinhados a 2 MB revelam a base do chunk ZendMM.

  2. Code Leak — um segundo OOM com padding diferente para sobrepor buckets da HashTable. O campo pDestructor (um ponteiro para zval_ptr_dtor dentro da libphp) vaza, nos dando o endereço base da libphp. Bypass de ASLR puramente in-band.

  3. Execução de código — reclamar PS(http_session_vars) com uma HashTable falsa cujo pDestructor aponta para zif_system. Quando o PHP limpa a HashTable, ele chama pDestructor(arg1) → system("cmd").

O exploit foi confirmado contra PHP 8.4.26 em x86_64, alcançando execução de comandos como www-data. O setup do lab usou memory_limit=8M, session.upload_progress.enabled=On e um único worker Apache (MaxRequestWorkers=1).

Também confirmamos crash/DoS confiável (10/10 SIGABRT) em PHP 8.2.34 (arm64) e PHP 8.4.26 (x86_64).

Técnica de trigger do OOM

O OOM é disparado através de uploads com nomes de arquivo vazios e nomes de campo profundamente aninhados:

Content-Disposition: form-data; name="f[0_0][0_1]...[0_14]"; filename=""

Cada upload com nome de arquivo vazio consome ~35 KB durante o parsing multipart, mas o corpo do upload pode ser vazio — mantendo o POST total abaixo de 1 MB enquanto esgota o memory limit. Com memory_limit=8M, cerca de 200–400 uploads são suficientes.

Versões corrigidas

A correção foi incluída no commit 3584a98bda e está disponível em:

  • PHP 8.2.35
  • PHP 8.3.36
  • PHP 8.4.27
  • PHP 8.5.12

Mitigação

Se não puder atualizar imediatamente:

  • Desabilite o upload progress de sessão: session.upload_progress.enabled=Off
  • Use PHP-FPM em vez de mod_php (o isolamento de processo impede reutilização do worker)
  • Mantenha max_file_uploads no padrão (20)
  • Nunca rode display_errors=On em produção

Créditos

O writeup original detalhado e a técnica de amplificação por nome de arquivo vazio foram documentados por Calif / Wiz (Ryan Austin — @lcfr_hax), incluindo a análise do fluxo BG(unserialize) e extensão para ambientes WordPress.

Análise, desenvolvimento do exploit e validação em laboratório realizados por Walleson Moura (@ph0r3nsic) na DeepLook Labs.

Peace!