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:
-
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 viaSet-Cookie. Os ponteiros alinhados a 2 MB revelam a base do chunk ZendMM. -
Code Leak — um segundo OOM com padding diferente para sobrepor buckets da HashTable. O campo
pDestructor(um ponteiro parazval_ptr_dtordentro da libphp) vaza, nos dando o endereço base da libphp. Bypass de ASLR puramente in-band. -
Execução de código — reclamar
PS(http_session_vars)com uma HashTable falsa cujopDestructoraponta parazif_system. Quando o PHP limpa a HashTable, ele chamapDestructor(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_uploadsno padrão (20) - Nunca rode
display_errors=Onem 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!