Guias de Segurança

O que verificar após uma atualização de emergência do PHP: PHP web vs. PHP de linha de comando, set -e e códigos de saída do diff, e uma nova verificação de invasão

Após uma correção urgente, o PHP web e o CLI podem diferir. Como verificar ambos, set -e com diff e a checagem de invasão.

Publicado 2026-11-01 Atualizado 2026-11-01 Última verificação 2026-11-01 14 min de leitura
Nesta página

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.

  1. Após o anúncio

    Trocar para a página de manutenção e aguardar a versão corrigida
  2. Após o lançamento

    Instalar a versão corrigida e retirar a página de manutenção
  3. Verificaçã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 reapontado
  4. Na 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 refeito
  5. Depois

    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

No mesmo servidor, o PHP usado pelo web e o PHP usado pela linha de comando podem ser programas separados, em versões separadas. Após a correção, verifique os dois.

Verifique o web e o CLI separadamente

1

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.

2

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.

3

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.

4

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).

5

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"

ComandoSignificado de 1Significado de 2Fonte
diffDiferenças encontradasProblemaGNU diffutils: diff
cmpDiferenças encontradasProblemaGNU diffutils: cmp
grepNenhuma linha selecionada (sem correspondência)Ocorreu um erroGNU 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 b simples sob set -e (interrompe só porque há diferenças)
  • diff a b || true (engole tanto "diferenças" quanto um erro real (2))
  • Usar || true no grep para 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 ;;
esac

O 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.

ItemO que procurarComando de exemplo
Chaves de loginChaves públicas que você não se lembra de ter cadastradoAbrir authorized_keys
UsuáriosUsuários desconhecidos, mudanças em grupos administrativosgetent passwd, getent group sudo
Arquivos modificados de pacotesArquivos instalados por um pacote que mudaram desde a distribuiçãodebsums -s (família Debian), rpm -Va (família RHEL)
Executáveis sem pacote donoNovos executáveis em /usr/local, /opt ou no PATH que nenhum pacote possuidpkg -S <arquivo> (família Debian) ou rpm -qf <arquivo> (família RHEL); procure arquivos sem pacote dono
SUIDNovos arquivos que executam com os privilégios do donofind / -xdev -perm -4000 -type f
cron e systemdTarefas e timers agendados desconhecidoscrontab -l, /etc/crontab, /etc/cron.d, systemctl list-timers
/tmpArquivos executáveis colocados alifind /tmp /var/tmp -type f -perm /111
Conexões de saídaConexões para destinos que você não reconhecess -tunp
Dentro de contêineresProcessos em execução e arquivos gravados após o iníciodocker 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.

1

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.

2

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.

3

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.

4

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

  1. Se você puder fazer login no servidor, execute php -v e which php, e escreva o caminho completo para o PHP nas suas tarefas do cron
  2. 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)
  3. Encontre, nos scripts com set -e, os lugares que chamam diff, grep ou cmp, e decida como tratar o status de saída 1
  4. Guarde uma cópia do que você publicou fora do servidor e compare-a após as atualizações
  5. Torne a lista de verificação de invasão idêntica em todos os servidores e percorra-a item por item

Fontes

Leia a seguir

FAQ

QO PHP pode continuar em uma versão antiga depois que eu o atualizei?
A

Sim. O PHP que processa requisições web (PHP-FPM ou um módulo do Apache) e o PHP de linha de comando (CLI) usado em um shell ou no cron podem ser programas separados, com versões diferentes. O php.net chama de SAPI a forma como o PHP é executado e afirma que o SAPI de CLI difere dos demais em muitos aspectos. O lado web pode ser corrigido enquanto o PHP usado pelo cron ainda está antigo.

QComo verifico qual versão do PHP está realmente em execução?
A

Para a linha de comando, execute php -v. Para a web, verifique pelo lado web; no WordPress, isso fica em Saúde do Site (Site Health) na tela de administração. Se você usar uma página phpinfo(), apague-a assim que terminar. which php mostra qual executável é chamado e, em sistemas no estilo Debian com várias versões, update-alternatives --display php mostra para onde o comando php aponta. Em tarefas do cron, escrever o caminho completo para o PHP elimina qualquer dúvida sobre qual deles é executado.

QPor que um script de shell com set -e para quando eu uso diff?
A

O diff retorna status de saída 1 quando encontra diferenças. O manual do GNU define 0 como nenhuma diferença, 1 como diferenças e 2 como problema. O set -e (errexit) interrompe o script em um status diferente de zero, portanto apenas encontrar uma diferença já o interrompe. Coloque o comando em uma condição if, ou capture o status de saída em uma variável e trate 0, 1 e 2 separadamente. O grep (1 significa nenhuma correspondência) e o cmp (1 significa diferente) se comportam da mesma forma.

QComo decido se fui invadido quando uma entrada de log parece suspeita?
A

O log sozinho não permite decidir. Um log registra uma tentativa que chegou, não necessariamente um sucesso. Compare seus arquivos públicos ativos com a cópia que você guardou no momento da implantação, e verifique se eles coincidem e se não existem arquivos PHP extras. A coincidência permite dizer que não foi encontrada adulteração de arquivos. Não é prova de segurança, então combine com as outras verificações.