Você já olhou seus servidores hoje? Se você roda PHP em produção, esta quarta-feira, 21 de outubro de 2026, não é uma quarta-feira normal.
O PHP2Shell são duas vulnerabilidades críticas no próprio PHP. Não é no Laravel, não é num pacote do Composer: é no motor, a parte que roda todo o resto. A notícia vazou antes da correção, as duas falhas permitem execução remota de código sem login, e a única saída segura é atualizar o binário do PHP. A seguir: o que aconteceu, quem está em risco, como os bugs funcionam e o que fazer agora.
Ouça o episódio
Este post tem um episódio pareado no Code With Accent, o podcast em inglês, com sotaque brasileiro, sobre código de produção: “Not a Normal Wednesday”. É a mesma história contada em inglês, com a caixa meio embalada e o cartão do hotel.
O que aconteceu: o plano e o vazamento
O time de segurança do PHP tinha duas falhas críticas em mãos e um plano: corrigir em sigilo, mandar a correção antes para as distribuições Linux e os vendors de LTS, e publicar tudo junto, correção e detalhes, na quinta-feira.
O plano não sobreviveu. Na manhã de terça, 20 de outubro, um post no Hacker News afirmou que duas falhas críticas do PHP estavam para ser divulgadas. O post foi apagado, mas os prints já circulavam nas redes. Poucas horas depois, a watchTowr confirmou que as falhas eram reais.
Logo em seguida, o time do PHP publicou um aviso: um release de segurança crítico, com duas falhas de nota 9.2 no CVSS v4.0, marcado para quarta-feira, 21 de outubro, às 14h UTC (11h em Brasília). Um dia antes do combinado.
Resultado: por quase um dia inteiro, a existência das falhas era pública e não havia correção. O que vazou foi a existência e a gravidade, não o código do ataque. Mesmo assim, quem acompanha notícia de segurança, atacante incluído, sabia mais que a maioria dos sysadmins.
Até a manhã de 21 de outubro, a Huntress não tinha visto exploração ativa na base de clientes dela, mas registrou relatos de ataques começando. Não espere a confirmação de um ataque para agir.
Quem está em risco
As falhas são a CVE-2026-109618 e a CVE-2026-109619. O apelido PHP2Shell, que circula entre pesquisadores, resume o resultado: do PHP direto para um shell no seu servidor. É execução remota de código, sem autenticação.
Segundo o advisory oficial, está em risco toda instalação de PHP que atende requisições web:
- PHP-FPM
- Apache com mod_php
- php-cgi
- FrankenPHP
- LiteSpeed
O risco é maior no mod_php. Ali já existe exploit demonstrado em que uma requisição sem autenticação dispara o ataque antes de qualquer código da aplicação rodar. A sua validação do Laravel e o seu middleware nem chegam a executar. É o chão da casa, não os móveis: você pode ter código limpo, testes e code review, e se o chão cede, cai tudo.
Descubra o que está rodando
O primeiro passo é saber quais versões você tem. Atenção: a CLI e o FPM podem ser binários diferentes, com versões diferentes.
# Versão da linha de comando
php -v
# Versão de cada PHP-FPM instalado (Debian/Ubuntu)
for bin in /usr/sbin/php-fpm*; do "$bin" -v | head -n 1; done
# O Apache está usando mod_php?
apache2ctl -M 2>/dev/null | grep -i php
Como os bugs funcionam
CVE-2026-109618: a caixa meio embalada
Ao receber uma requisição, o PHP monta estruturas internas para ela. Pense numa caixa sendo embalada para entrega. Se a requisição estoura o memory_limit no meio dessa montagem, o PHP é interrompido de repente e a caixa fica pela metade.
O bug vem depois: na limpeza, o PHP trata essa caixa incompleta como se estivesse pronta e válida. Com uma requisição bem construída, o atacante transforma essa confusão em execução de código. No advisory, a falha aparece como “RCE via OOM corruption”.
CVE-2026-109619: o cartão do hotel depois do check-out
É como o cartão do hotel que continua abrindo o quarto depois do check-out. Um estado interno de uma requisição sobrevive depois que a memória dele foi liberada. A próxima requisição atendida pelo mesmo worker (o processo do PHP que fica vivo e atende uma requisição atrás da outra) usa esse estado velho antes de a inicialização normal resetá-lo. No advisory: “RCE via stale global state”.
A raiz comum
As duas têm a mesma raiz: um erro fatal interrompe o PHP no meio de alguma coisa, e a limpeza confia no que não deveria. Por isso as configurações ligadas a memória, tamanho de POST e upload progress importam nas mitigações. Este script mostra como elas estão no SAPI que atende a web:
<?php
// php2shell-ini.php: abra pelo navegador (via FPM ou mod_php), não só pela CLI.
// Apague o arquivo assim que terminar.
$keys = [
'memory_limit',
'post_max_size',
'upload_max_filesize',
'session.upload_progress.enabled',
];
if (PHP_VERSION_ID >= 80500) {
$keys[] = 'fatal_error_backtraces';
}
header('Content-Type: text/plain; charset=utf-8');
echo 'SAPI: ' . PHP_SAPI . "\n";
echo 'PHP: ' . PHP_VERSION . "\n\n";
foreach ($keys as $key) {
$value = ini_get($key);
echo str_pad($key, 34) . ($value === false ? '(não existe)' : var_export($value, true)) . "\n";
}
O que fazer hoje
1. Atualize
| Ramo | Versão corrigida |
|---|---|
| 8.5 | 8.5.12 |
| 8.4 | 8.4.27 |
| 8.3 | 8.3.36 |
| 8.2 | 8.2.35 |
Se você usa os pacotes da sua distribuição, confira com o vendor: as distribuições Linux e os vendors de LTS receberam os patches antes. Um fix backportado pode não mudar o número da versão, então leia o changelog do pacote em vez de confiar só no número.
Se você instala pelo repositório Remi (rpms.remirepo.net), comum em servidores RHEL, AlmaLinux e Rocky Linux: no dia do release, o próprio advisory avisou que o repositório não teria a atualização naquele dia e recomendou migrar para os pacotes da distribuição ou compilar a partir do código-fonte.
O script abaixo compara a versão do PHP com os releases oficiais. Ele vale para a CLI; para o FPM, compare a saída do php-fpm -v da seção anterior com a tabela acima.
<?php
// check-php2shell.php
// Compara a versão do PHP com as versões corrigidas do PHP2Shell.
// Atenção: pacotes de distro podem ter o patch "backportado" sem mudar o número.
$fixed = [
'8.5' => '8.5.12',
'8.4' => '8.4.27',
'8.3' => '8.3.36',
'8.2' => '8.2.35',
];
$branch = PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION;
if (!isset($fixed[$branch])) {
echo 'PHP ' . PHP_VERSION . ": sem release oficial corrigido. Use o backport, o vendor ou atualize.\n";
exit(2);
}
if (version_compare(PHP_VERSION, $fixed[$branch], '>=')) {
echo 'PHP ' . PHP_VERSION . ": versão corrigida.\n";
exit(0);
}
echo 'PHP ' . PHP_VERSION . ": VULNERÁVEL, atualize para {$fixed[$branch]}.\n";
exit(1);
2. Está no PHP 8.1 ou anterior?
O projeto PHP não lança mais updates para essas versões. Algumas distribuições e vendors de LTS oferecem correções backportadas, então fale com o seu. E, desta vez, o time de segurança publicou backports mínimos de código-fonte para quem compila o PHP ou não tem um vendor de LTS: 7.4, 8.0 e 8.1. Use e planeje a migração.
E lembre: o PHP 8.2 chega ao fim de vida em 31 de dezembro de 2026. Depois dessa data, não recebe mais correção de segurança. Se você está no 8.2, atualize agora para o 8.2.35 e já planeje o próximo passo.
3. Não dá para atualizar agora? Mitigações
O advisory é claro: só a atualização resolve. As medidas abaixo reduzem a exposição a caminhos de ataque específicos, mas não deixam a instalação segura. Elas precisam valer antes do parsing da requisição (php.ini, pool do FPM ou configuração do servidor), então nada de ini_set() nem .user.ini.
Para a CVE-2026-109618 (memória):
memory_limit = -1, de preferência num lugar que a aplicação não consiga sobrescrever (php_admin_valueno pool do FPM ou na configuração do Apache). Confira que nenhum pool, diretório ou aplicação recoloca um limite finito. Isso remove a falha controlável de limite de memória, mas uma falha de alocação de baixo nível ainda pode chegar ao caminho vulnerável.- Com
memory_limit = -1, use um limite de container ou cgroup como barreira, de preferência com uma política que mate só o worker afetado. Não useRLIMIT_ASnemRLIMIT_DATAno lugar: eles podem causar justamente a falha de alocação vulnerável. post_max_sizebaixo:8Mse a aplicação não recebe upload,32Mse recebe, sempre comupload_max_filesizeabaixo dele. Vale também limitar no servidor web ou no proxy o tamanho da requisição decodificada e a quantidade de campos, de partes multipart, de headers e de níveis de aninhamento, inclusive para requisições sem umContent-Lengthutilizável.
Para a CVE-2026-109619 (estado velho):
session.upload_progress.enabled = 0e reinicie todos os workers. Isso bloqueia os caminhos confirmados via sessão, não outras variantes.- No PHP 8.5,
fatal_error_backtraces = 0e reinicie todos os workers. Isso bloqueia só o caminho confirmado via backtrace de erro fatal. - Remova configurações inválidas ou depreciadas aplicadas por requisição que emitem diagnósticos antes da inicialização. Esconder o diagnóstico com
error_reporting,display_errorsou@não ajuda. - Corrija erros fatais e timeouts de execução que um atacante consegue provocar, inclusive em callbacks, no processamento de upload ou importação e na limpeza.
Num pool do FPM, fica assim:
; /etc/php/8.3/fpm/pool.d/www.conf: mitigação TEMPORÁRIA, não substitui o update
php_admin_value[memory_limit] = -1
php_admin_value[post_max_size] = 8M
php_admin_value[upload_max_filesize] = 4M
php_admin_flag[session.upload_progress.enabled] = off
; No PHP 8.5, adicione também:
; php_admin_flag[fatal_error_backtraces] = off
Depois, reinicie todos os workers (no Debian/Ubuntu, por exemplo, sudo systemctl restart php8.3-fpm).
No Apache com mod_php, as mesmas diretivas vão na configuração do Apache (no VirtualHost, por exemplo), com outra sintaxe, e o Apache precisa ser reiniciado (sudo systemctl restart apache2):
# mitigação TEMPORÁRIA, não substitui o update
php_admin_value memory_limit -1
php_admin_value post_max_size 8M
php_admin_value upload_max_filesize 4M
php_admin_flag session.upload_progress.enabled off
4. composer update não resolve
O Composer atualiza seus pacotes: Laravel, Symfony, suas bibliotecas. O PHP2Shell mora no binário do PHP. O Composer troca os móveis; desta vez, você precisa consertar o chão.
Resumo
- O PHP2Shell são duas falhas críticas no motor do PHP, e a existência delas vazou antes do patch.
- A CVE-2026-109618 corrompe dados depois de um erro de falta de memória; a CVE-2026-109619 mantém estado velho vivo entre requisições no mesmo worker.
- A resposta é atualizar hoje para 8.5.12, 8.4.27, 8.3.36 ou 8.2.35. Mitigações só compram tempo.
Desafio da semana: abra um terminal em cada servidor que você tem e rode php -v. Faça uma lista. Toda versão abaixo das corrigidas vai para o topo da sua lista de tarefas. E confira o PHP-FPM também, não só a CLI: o terminal pode dizer “corrigido” e o site continuar rodando o binário velho.






