Público-alvo: pessoas que administram sites em PHP e estão aplicando uma atualização de emergência do PHP, ou acabaram de fazê-la e se preocupam com lacunas.
Em outubro de 2026, foram lançadas versões corrigidas para duas vulnerabilidades críticas do PHP (CVE-2026-109618 e CVE-2026-109619). Um site administrado pelo operador deste site foi atualizado na mesma noite. Esta página transforma o que aconteceu ali, duas premissas iniciais que se mostraram erradas e a nova verificação de invasão aplicada a todos os servidores depois, em passos gerais. As vulnerabilidades e as versões corrigidas são abordadas em Duas vulnerabilidades críticas do PHP e as versões corrigidas de emergência (outubro de 2026).
O que aconteceu (em um site administrado pelo operador deste site)
O site usa PHP em mais de um ambiente. Como a equipe do PHP havia anunciado quando as versões corrigidas seriam lançadas, o operador trocou para uma página de manutenção antes do lançamento e ficou offline até que a versão corrigida estivesse disponível. Depois disso, ele instalou a versão corrigida e retirou a página de manutenção.
Após o anúncio
Trocar para a página de manutenção e aguardar a versão corrigidaApós o lançamento
Instalar a versão corrigida e retirar a página de manutençãoVerificação pós-atualização
O lado web foi atualizado, mas o PHP de linha de comando usado pelo cron ainda apontava para uma versão antiga, então foi reapontadoNa mesma noite
Um script temporário que retirava o modo de manutenção parou antes de gravar qualquer coisa. Causa: set -e combinado com diff. Nenhum dano foi causado, e o script foi refeitoDepois
A mesma lista de verificação de invasão foi aplicada a todos os servidores, e uma requisição suspeita que havia chegado antes da atualização foi verificada comparando arquivos
A CISA dos EUA depois incluiu as duas CVEs em seu catálogo de vulnerabilidades conhecidas como exploradas (KEV) (comunicado da CISA). Isso apoia a decisão de corrigir com pressa, mas uma correção apenas fecha a porta. Verificar se alguém já tinha entrado é uma tarefa separada.
Premissa 1: "O PHP web está atualizado, então terminamos"
A primeira premissa era de que, se o site roda na nova versão, todo o PHP daquele servidor está atualizado. Na prática, o PHP de linha de comando chamado pelo cron ainda apontava para uma versão antiga.
O PHP pode ser um programa diferente conforme a forma como é executado (o SAPI, Server API). O php.net observa que o SAPI de CLI difere dos outros SAPIs em muitos aspectos (PHP: Usando o PHP pela linha de comando). Com várias versões instaladas em uma mesma máquina, o web pode rodar uma versão nova enquanto a linha de comando roda uma antiga.
Requisições web → PHP-FPM / módulo do Apache
Executa a versão atualizada (visível em Saúde do Site e telas semelhantes)
cron e scripts de shell → PHP de linha de comando
Ainda aponta para a versão antiga, o que uma verificação pelo lado web não consegue mostrar
Verifique o web e o CLI separadamente
Verifique a versão do PHP de linha de comando
php -v mostra a versão do PHP que este shell chama. O php.net diz que php -v também informa se o PHP é a versão CGI ou CLI. which php mostra a localização do executável que de fato é chamado.
Em sistemas no estilo Debian com várias versões, verifique para onde o comando aponta
update-alternatives --display php mostra para qual versão o comando php aponta (manual do update-alternatives). --config altera isso, mas é uma mudança de configuração do servidor, então confirme antes o procedimento e como desfazê-lo.
Escreva o caminho completo para o PHP nas tarefas do cron
O cron executa tarefas em seu próprio ambiente, separado de um shell de login (crontab(5)). Se você escrever apenas php, o PHP escolhido depende desse ambiente. Conselho deste site: escreva o caminho completo para a versão que você corrigiu, para que a verificação pós-atualização tenha uma única resposta.
Verifique a versão do PHP web pelo lado web
No WordPress, Saúde do Site na tela de administração mostra isso. No código, estão disponíveis as constantes PHP_VERSION (versão) e PHP_SAPI (como o PHP é executado: cli para o CLI, fpm-fcgi para o PHP-FPM, apache2handler para o módulo do Apache) (php_sapi_name).
Apague qualquer página phpinfo() logo após a verificação
Um arquivo público que chama phpinfo() mostra a versão, mas também expõe detalhes de configuração e internos do servidor. Apague-o assim que terminar. Não deixá-lo no ar é a condição para usar esse método.
Premissa 2: "o diff ou funciona ou falha"
O script temporário que retirava o modo de manutenção foi escrito para comparar o conteúdo atual com o novo usando diff antes de gravar qualquer coisa. Ele tinha set -e ativado.
O diff retorna status de saída 1 quando encontra diferenças. No manual do GNU, 0 significa nenhuma diferença, 1 significa que diferenças foram encontradas, e 2 significa problema (GNU diffutils: diff). Encontrar diferenças era exatamente o que o script esperava. Mas o set -e (errexit) interrompe o shell em um status diferente de zero, exceto em lugares como condições. O script parou porque havia diferenças e nunca chegou à gravação.
Assim, a etapa que retirava o modo de manutenção parou sem gravar nada, e não houve dano. Ela falhou pelo lado seguro, mas a combinação inversa, não parar onde deveria, seria perigosa. O script foi refeito.
Comandos em que o status de saída 1 não é um "erro"
| Comando | Significado de 1 | Significado de 2 | Fonte |
|---|---|---|---|
diff | Diferenças encontradas | Problema | GNU diffutils: diff |
cmp | Diferenças encontradas | Problema | GNU diffutils: cmp |
grep | Nenhuma linha selecionada (sem correspondência) | Ocorreu um erro | GNU grep |
Padrões seguros
O manual do GNU Bash afirma que, com set -e, o shell não termina quando um comando retorna valor diferente de zero como parte de um teste if, em uma lista && ou || que não seja o último comando, ou quando seu status é invertido com ! (GNU Bash: The Set Builtin). Use isso.
Interrompe, ou esconde uma falha real
- Escrever um
diff a bsimples sobset -e(interrompe só porque há diferenças) diff a b || true(engole tanto "diferenças" quanto um erro real (2))- Usar
|| truenogreppara que "sem correspondência" e "não foi possível ler o arquivo" pareçam iguais
Trate os códigos de saída separadamente
- Capture o status de saída em uma variável e ramifique em 0, 1 e 2 (o padrão recomendado)
- Se você só precisa saber se há diferenças,
if diff -q a b; then …funciona, mas note que 2 (um erro) cai do mesmo lado de "diferenças" - Se usar
|| true, decida primeiro se perder um 2 (um erro real) é aceitável
Aqui está um exemplo que separa 0, 1 e 2. Uma falha no lado esquerdo de || está isenta do errexit, então o script não para e rc guarda o status de saída.
#!/usr/bin/env bash
set -euo pipefail
rc=0
diff -rq deployed/ live/ || rc=$?
case "$rc" in
0) echo "no differences" ;;
1) echo "differences: review before continuing" ;;
*) echo "diff failed" >&2; exit 2 ;;
esacO set -e não é uma rede de segurança completa. Ele tem muitas exceções que podem esconder erros, então, para ramificações importantes, é mais fácil ler o status de saída você mesmo e decidir (conselho deste site).
Após a correção: execute as mesmas verificações em todos os servidores
A correção fecha a porta de entrada. Ela não diz se alguém entrou antes da atualização. Para vulnerabilidades do KEV, a CISA pede uma verificação de invasão antes de atualizar (veja "Exploração confirmada e inclusão no KEV da CISA" em o artigo sobre PHP). Para que as verificações não variem de servidor para servidor, o operador aplicou a mesma lista de verificação a todos os servidores.
| Item | O que procurar | Comando de exemplo |
|---|---|---|
| Chaves de login | Chaves públicas que você não se lembra de ter cadastrado | Abrir authorized_keys |
| Usuários | Usuários desconhecidos, mudanças em grupos administrativos | getent passwd, getent group sudo |
| Arquivos modificados de pacotes | Arquivos instalados por um pacote que mudaram desde a distribuição | debsums -s (família Debian), rpm -Va (família RHEL) |
| Executáveis sem pacote dono | Novos executáveis em /usr/local, /opt ou no PATH que nenhum pacote possui | dpkg -S <arquivo> (família Debian) ou rpm -qf <arquivo> (família RHEL); procure arquivos sem pacote dono |
| SUID | Novos arquivos que executam com os privilégios do dono | find / -xdev -perm -4000 -type f |
| cron e systemd | Tarefas e timers agendados desconhecidos | crontab -l, /etc/crontab, /etc/cron.d, systemctl list-timers |
| /tmp | Arquivos executáveis colocados ali | find /tmp /var/tmp -type f -perm /111 |
| Conexões de saída | Conexões para destinos que você não reconhece | ss -tunp |
| Dentro de contêineres | Processos em execução e arquivos gravados após o início | docker top, docker diff |
O crontab -l mostra apenas as tarefas do usuário atual, então verifique /etc/crontab, /etc/cron.d e os spools de cron por usuário separadamente. O ss -tunp precisa de root para mostrar o processo por trás de cada conexão. dpkg -S e rpm -qf informam qual pacote possui um arquivo; "nenhum pacote dono" pode ainda ser um arquivo que você mesmo colocou, então confira com o que você se lembra de ter instalado.
find -perm -4000 encontra arquivos que têm todos os bits indicados ativados (find(1)). docker diff lista arquivos alterados desde a criação do contêiner (A: adicionado, C: alterado, D: excluído), e docker top exibe os processos em execução (docker container diff, docker container top). Contêineres normalmente são reconstruídos em vez de modificados, então trate um executável mostrado pelo docker diff como inesperado e investigue (conselho deste site).
Como ler cada item, e os passos para hospedagem compartilhada, WordPress e VPS, estão em Como verificar se seu site ou servidor foi invadido.
Esclareça uma entrada de log suspeita comparando arquivos
Durante essa verificação, a equipe descobriu que, antes da atualização, um POST havia chegado ao formulário de contato vindo de um nó de saída do Tor (um ponto de saída de uma rede que oculta o remetente). Um log só mostra que a requisição chegou. Ele não mostra se algo foi gravado em consequência.
Então eles compararam os arquivos públicos ativos com a cópia guardada no momento da implantação. Eles coincidiram exatamente, e não havia arquivos PHP extras. Essa verificação não encontrou nenhum sinal de que algum arquivo tenha sido alterado.
Guarde uma cópia do que você implantou, fora do servidor
Se a única coisa para comparar estiver no próprio servidor, uma alteração feita ao mesmo tempo passaria despercebida. A cada implantação, guarde o estado publicado fora do servidor.
Compare com os arquivos ativos
Use diff -rq para encontrar arquivos que diferem, ou compare listas de hashes por arquivo (sha256sum). Se algo for diferente, verifique se é uma mudança que você pretendia.
Procure também arquivos adicionados
Mesmo quando o conteúdo coincide, um arquivo PHP recém-colocado não está na cópia original, então precisa ser procurado separadamente. O diff -rq também informa arquivos que existem apenas de um lado.
Uma coincidência não é prova de segurança
Este método só mostra que os arquivos comparados não foram alterados. Ele não vê o banco de dados, as pastas de upload ou alterações fora do servidor. Avalie-o junto com as outras verificações (conselho deste site).
A visão deste site: defina a lista de verificação antes da noite de emergência
Em uma noite apressada, o que se observa em qual servidor tende a variar. Se a lista de itens for definida antecipadamente, todos os servidores recebem as mesmas verificações e as lacunas ficam visíveis. O PHP de linha de comando antigo foi encontrado desta vez porque a verificação pós-atualização não parou no lado web.
O que fazer hoje
- Se você puder fazer login no servidor, execute
php -vewhich php, e escreva o caminho completo para o PHP nas suas tarefas do cron - Verifique a versão do PHP web pelo lado web, como em Saúde do Site (e apague qualquer página
phpinfo()logo após o uso) - Encontre, nos scripts com
set -e, os lugares que chamamdiff,grepoucmp, e decida como tratar o status de saída 1 - Guarde uma cópia do que você publicou fora do servidor e compare-a após as atualizações
- Torne a lista de verificação de invasão idêntica em todos os servidores e percorra-a item por item
Fontes
- PHP: Usando o PHP pela linha de comando
- PHP: php_sapi_name
- GNU Bash Reference Manual: The Set Builtin
- GNU diffutils: diff, cmp, GNU grep
- update-alternatives(1), crontab(5), find(1)
- docker container diff, docker container top
- CISA: adds two vulnerabilities to the Known Exploited Vulnerabilities Catalog
Leia a seguir
- As vulnerabilidades e as versões corrigidas: Duas vulnerabilidades críticas do PHP e as versões corrigidas de emergência (outubro de 2026)
- Verificação de invasão: Como verificar se seu site ou servidor foi invadido
- Preparação: Essenciais de backup e recuperação / Lista de verificação de inventário de segurança
- Termos: O que é RCE (execução remota de código)? / O que é um IOC (indicador de comprometimento)?