Ir para o conteúdo principal
Github

Vulnerabilidade do PHP CVE-2026-109619

O PHP contém diversos estados globais, como estados de erro, sessão, objetos e extensões, que são mantidos por um processo trabalhador (*worker*), mas apontam para memória pertencente a uma única requisição. Esses estados devem ser redefinidos antes que a memória da requisição seja liberada. Algumas operações de processamento de entradas, configurações, sessões e erros são executadas antes da inicialização normal da próxima requisição. Essas operações acessam inadvertidamente estados obsoletos que não foram redefinidos pela requisição anterior, antes que a inicialização tenha a oportunidade de substituí-los, causando um uso após liberação (*use-after-free*). Uma disposição estrategicamente elaborada do heap pode permitir a execução remota de código (RCE) completa.

O estado pode persistir por mais de um motivo. Um erro fatal, inclusive aquele causado por esgotamento de memória, pode interromper uma operação antes que ela execute a redefinição correspondente. Um aviso ou outro diagnóstico não fatal também pode preencher novamente o estado de erro depois que ele já tiver sido limpo durante o encerramento. O heap da requisição é então liberado enquanto o campo global persistente do processo trabalhador continua apontando para ele. Uma operação posterior, executada antes da inicialização normal da requisição, pode consumir esse valor obsoleto.

A exploração bem-sucedida dessa vulnerabilidade exige tanto um produtor quanto um consumidor. O produtor é uma operação do PHP que armazena um estado pertencente a uma requisição em um local que o PHP pode acessar posteriormente, durante uma requisição subsequente. O consumidor é uma operação posterior do PHP que lê, libera, toma decisões condicionais ou age de outra forma com base no estado obsoleto previamente armazenado pelo produtor.

Uma vulnerabilidade concreta surgiria nas seguintes condições:

  1. Uma operação acessível do PHP armazena um estado pertencente à requisição em um local globalmente acessível, atuando como produtor.
  2. O estado não é redefinido, ou outra operação o preenche novamente após a redefinição normal.
  3. O PHP libera a memória da requisição enquanto o processo trabalhador persistente mantém o estado global que aponta para ela.
  4. O mesmo processo trabalhador atende a outra requisição.
  5. Uma operação do PHP lê, libera, toma decisões condicionais ou age de outra forma com base no estado obsoleto antes que a inicialização normal da requisição o redefina, atuando como consumidor.

As operações demonstradas de produção e consumo ocorrem em requisições HTTP diferentes, atendidas pelo mesmo processo trabalhador. As versões exatas afetadas, as configurações e as medidas de mitigação dependem do estado interno afetado pela operação produtora.

Resolução

Versões afetadas Versões corrigidas
< 8.2.35 8.2.35
< 8.3.36 8.3.36
< 8.4.27 8.4.27
< 8.5.12 8.5.12

Vulnerabilidade

Produtores

Uma operação produtora torna-se perigosa quando o estado pertencente à requisição continua publicado após a liberação do heap da requisição. Um erro fatal pode causar isso ao interromper a operação antes que ela redefina o estado. As interrupções fatais incluem falhas de análise sintática e compilação, erros não capturados, falhas de require e Maximum execution time exceeded (tempo máximo de execução excedido).

O esgotamento de memória (OOM) pode provocar a mesma interrupção quando um estado afetado de erro, exceção, objeto ou extensão já tiver sido publicado.

Até mesmo um diagnóstico não fatal, emitido por uma extensão ou durante a limpeza do servidor em uma etapa posterior do encerramento, pode preencher novamente o estado global com novas informações de erro pertencentes à requisição.

Consumidores

Há dois consumidores confirmados que são executados antes da inicialização normal.

Diagnósticos de INI antes da inicialização da requisição

Um aviso, uma notificação ou uma mensagem de obsolescência durante a inicialização pode consumir um estado de erro obsoleto. Não é necessário que o aviso seja fatal, exibido ou registrado em log. Esse tipo de aviso pode ser gerado ao aplicar configurações INI inválidas ou obsoletas antes da inicialização normal do executor. Os exemplos incluem:

  • Avisos de obsolescência determinísticos, como auto_detect_line_endings=1, um valor de filter.default diferente do padrão, configurações legadas de assert.* no PHP 8.3 ou superior, configurações legadas de sessão no PHP 8.4 ou superior e report_memleaks=0 ou valores não nulos de intl.error_level no PHP 8.5.
  • Valores explicitamente configurados de mbstring.http_input, mbstring.http_output, mbstring.internal_encoding, iconv.input_encoding, iconv.output_encoding ou iconv.internal_encoding.
  • Valores inválidos ou conflitantes de zend.assertions, memory_limit, date.timezone e configurações de DBA, EXIF, e-mail, OpenSSL, zlib e extensões carregadas.
  • Quantidades malformadas ou que causam estouro de limite, incluindo valores inválidos de memory_limit, tamanho do corpo da requisição, entradas, tempo limite, pilha e extensões no PHP 8.2 ou superior.
  • Caminhos configurados para logs, e-mail, sessões ou cache SOAP que são rejeitados pela diretiva open_basedir.

O estado global é acessado antes que o PHP decida se deve emitir, exibir ou registrar o aviso em log. Alterar error_reporting, usar @ ou desabilitar display_errors ou log_errors não protege contra essa vulnerabilidade.

Possibilidade de exploração

Com o Apache mod_php, as configurações de diretório em .htaccess e as diretivas Apache php_value, php_flag e php_admin_* são aplicadas antes de php_request_startup(). O arquivo php.ini normal contém configurações de inicialização e não é reaplicado como consumidor entre requisições.

O FPM fica condicionalmente exposto quando o nginx, o Apache mod_proxy_fcgi ou outro componente de front-end envia parâmetros PHP_VALUE ou PHP_ADMIN_VALUE que geram um diagnóstico antes da inicialização da requisição. Os valores do pool FPM são aplicados quando um processo trabalhador é iniciado, e o arquivo .user.ini é aplicado após a inicialização normal. Eles não fornecem o mesmo consumidor antecipado. O FPM também encerra um processo filho quando php_request_startup() retorna uma falha.

O LSAPI pode aplicar antecipadamente valores INI especiais provenientes do ambiente e, portanto, é um possível caso de exposição condicional. Não foi identificado um consumidor equivalente de configuração antecipada na configuração padrão do FrankenPHP. Isso não significa que esse ambiente seja imune. Ele pode expor consumidores diferentes por meio do comportamento da aplicação ou das extensões.

Progresso de upload de sessão antes da inicialização normal

O mecanismo de progresso de upload de sessão pode alcançar o processamento de sessões antes da inicialização normal da sessão. Portanto, ele pode consumir estados de sessão ou de decodificação deixados por uma requisição interrompida antes que a inicialização normal os redefina. Esse consumidor é independente do caminho de diagnóstico INI e não exige um diagnóstico proveniente da configuração.

Casos concretos e possíveis

Backtrace de erro fatal no PHP 8.5: confirmado

Quando fatal_error_backtraces está habilitado, o relatório de erros fatais armazena informações de backtrace pertencentes à requisição. A redefinição desse estado global é sempre ignorada durante o encerramento. Um diagnóstico em uma requisição subsequente alcança o valor obsoleto antes que a inicialização normal da requisição o redefina.

Esse é o caso demonstrado mais sólido e constitui a base da exploração de RCE confirmada no mod_php.

Ciclo de vida do progresso de upload de sessão: confirmado

Um upload pode inicializar estados de sessão e de decodificação pertencentes à requisição antes da inicialização normal dos módulos. Um esgotamento de memória ou outro erro fatal ocorrido após a publicação desse estado pode impedir a redefinição correspondente. O PHP então libera o heap da requisição enquanto as variáveis globais persistentes de sessão ou decodificação continuam apontando para ele. Uma operação posterior de progresso de upload alcança essas variáveis globais antes da inicialização normal e as trata como se contivessem um estado válido.

O mecanismo de acionamento demonstrado, independente da aplicação, é o esgotamento de memória durante o processamento das entradas. Outros erros fatais nessa etapa podem provocar a mesma transição de estado, mas geralmente exigem condições adicionais relacionadas à aplicação, às extensões ou à configuração.

Com mod_php, tanto a operação produtora quanto o erro fatal podem ocorrer durante a inicialização do processamento multipart. O FPM encerra um processo filho quando essa inicialização falha, portanto essa sequência exata não persiste. O caminho validado no FPM, em vez disso, publica o estado durante a execução da aplicação, após uma inicialização bem-sucedida, encontra um erro fatal e, em seguida, utiliza o progresso de upload de sessão como consumidor antecipado. Isso funciona com um pool FPM dinâmico normal, embora dependa de uma operação adequada da aplicação.

O progresso de upload de sessão está habilitado por padrão, e não é necessária nenhuma chamada da aplicação a session_start(). Esse mecanismo pode atuar como produtor, consumidor ou ambos, mas a exploração ainda exige uma interrupção após a publicação do estado e uma requisição subsequente atendida pelo mesmo processo trabalhador.

Outros estados de erro, exceção, objeto e extensão: condicionais

As versões de PHP com suporte contêm outros estados de requisição que podem se tornar inseguros após a transição entre requisições. A correção completa redefine esse conjunto mais amplo de estados, pois eles não devem persistir em operações executadas antes da inicialização normal. A possibilidade de exploração depende da aplicação, das extensões e do ponto exato em que o estado foi publicado.

Erros fatais dentro de operações do PHP que chamam de volta a aplicação merecem atenção especial. A função de retorno de chamada (*callback*) pode introduzir um erro fatal enquanto a operação ainda mantém um estado temporário. A aplicabilidade depende da existência de uma rota acessível ao invasor, das funções de retorno de chamada registradas pela aplicação ou extensão e de um consumidor posterior executado antes da inicialização normal da requisição. Não existe uma medida de mitigação baseada em configuração que cubra todas essas operações.

Mitigação

Para instalações do PHP 8.5 que não podem ser atualizadas, defina administrativamente fatal_error_backtraces=0 e reinicie todos os processos trabalhadores. Isso elimina o produtor utilizado no caso confirmado de RCE. A medida não cobre outros estados globais ou de extensões pertencentes à requisição.

Para o caso relacionado ao ciclo de vida da sessão, defina administrativamente session.upload_progress.enabled=0 e reinicie todos os processos trabalhadores. Isso impede as operações antecipadas demonstradas de produção e consumo relacionadas à sessão. A medida não mitiga os outros caminhos do ciclo de vida. Pode ser prudente definir session.upload_progress.enabled=0 caso esse recurso não seja necessário, mesmo após a aplicação da correção, pois mais invasores podem tentar encontrar falhas nesse recurso pouco utilizado e pouco testado.

Reduza o risco restante interrompendo o maior número possível de etapas desse ciclo de vida:

  1. Elimine erros fatais em caminhos acessíveis a um invasor. Inclua falhas que dependam do fluxo da requisição em operações de upload, importação, páginas de erro, encerramento, destrutores, serialização, manipuladores de saída, plugins e extensões, em vez de considerar apenas as falhas observadas durante requisições comuns de páginas.
  2. Elimine avisos e outros diagnósticos emitidos durante a etapa posterior à desativação de extensões e durante a limpeza do servidor, após o estado normal de erro da requisição já ter sido limpo.
  3. Elimine diagnósticos de todas as configurações INI aplicadas antes da inicialização da requisição. Prefira php.ini, configurações do pool FPM ou configurações do servidor aplicadas na inicialização, em vez de substituições por requisição, e remova diretivas obsoletas.
  4. Se o desempenho não for um fator determinante, considere limitar o número de requisições que um processo trabalhador pode executar usando pm.max_requests=1.

Um tempo limite de execução do PHP deve ser tratado como outro erro fatal. Impeça que requisições controladas por invasores atinjam previsivelmente Maximum execution time exceeded enquanto o PHP estiver dentro de uma operação afetada. Alterar o limite de tempo apenas elimina essa possível interrupção e não mitiga os demais casos.

Auditoria de logs e configurações

Durante uma auditoria isolada, habilite log_errors=On, configure um error_log funcional e defina error_reporting=E_ALL na configuração de inicialização. Examine os logs do Apache, PHP-FPM, proxy reverso e serviços em busca de:

  • Avisos, mensagens de obsolescência e erros do núcleo repetidos antes da execução dos scripts de destino.
  • Avisos e outros diagnósticos produzidos durante a limpeza de extensões ou do servidor.
  • Mensagens de erros fatais, erros de análise sintática e erros não capturados.
  • Maximum execution time exceeded.
  • Falhas restritas a rotas, tipos de conteúdo, arquivos, implementações de funções de retorno de chamada ou opções de plugins controladas por um invasor.

Corrigir os erros observados reduz a exposição, mas a ausência de erros nos logs não comprova a segurança. Os logs históricos podem ter sido coletados com mensagens de obsolescência ou outras severidades relevantes excluídas. Mesmo uma auditoria com E_ALL cobre apenas as rotas e os estados da aplicação que foram exercitados. Uma falha também pode ocorrer antes que o PHP registre a mensagem. Os logs devem complementar uma auditoria estática da configuração e do fluxo da aplicação.

Mitigação

Para instalações do PHP 8.5 que não podem ser atualizadas, defina administrativamente fatal_error_backtraces=0 e reinicie todos os processos trabalhadores. Isso elimina o produtor utilizado no caso confirmado de RCE. A medida não cobre outros estados globais ou de extensões pertencentes à requisição.

Para o caso relacionado ao ciclo de vida da sessão, defina administrativamente session.upload_progress.enabled=0 e reinicie todos os processos trabalhadores. Isso impede as operações antecipadas demonstradas de produção e consumo relacionadas à sessão. A medida não mitiga os outros caminhos do ciclo de vida. Pode ser prudente definir session.upload_progress.enabled=0 caso esse recurso não seja necessário, mesmo após a aplicação da correção, pois mais invasores podem tentar encontrar falhas nesse recurso pouco utilizado e pouco testado.

Reduza o risco restante interrompendo o maior número possível de etapas desse ciclo de vida:

  1. Elimine erros fatais em caminhos acessíveis a um invasor. Inclua falhas que dependam do fluxo da requisição em operações de upload, importação, páginas de erro, encerramento, destrutores, serialização, manipuladores de saída, plugins e extensões, em vez de considerar apenas as falhas observadas durante requisições comuns de páginas.
  2. Elimine avisos e outros diagnósticos emitidos durante a etapa posterior à desativação de extensões e durante a limpeza do servidor, após o estado normal de erro da requisição já ter sido limpo.
  3. Elimine diagnósticos de todas as configurações INI aplicadas antes da inicialização da requisição. Prefira php.ini, configurações do pool FPM ou configurações do servidor aplicadas na inicialização, em vez de substituições por requisição, e remova diretivas obsoletas.
  4. Se o desempenho não for um fator determinante, considere limitar o número de requisições que um processo trabalhador pode executar usando pm.max_requests=1.

Um tempo limite de execução do PHP deve ser tratado como outro erro fatal. Impeça que requisições controladas por invasores atinjam previsivelmente Maximum execution time exceeded enquanto o PHP estiver dentro de uma operação afetada. Alterar o limite de tempo apenas elimina essa possível interrupção e não mitiga os demais casos.

Auditoria de logs e configurações

Durante uma auditoria isolada, habilite log_errors=On, configure um error_log funcional e defina error_reporting=E_ALL na configuração de inicialização. Examine os logs do Apache, PHP-FPM, proxy reverso e serviços em busca de:

  • Avisos, mensagens de obsolescência e erros do núcleo repetidos antes da execução dos scripts de destino.
  • Avisos e outros diagnósticos produzidos durante a limpeza de extensões ou do servidor.
  • Mensagens de erros fatais, erros de análise sintática e erros não capturados.
  • Maximum execution time exceeded.
  • Falhas restritas a rotas, tipos de conteúdo, arquivos, implementações de funções de retorno de chamada ou opções de plugins controladas por um invasor.

Corrigir os erros observados reduz a exposição, mas a ausência de erros nos logs não comprova a segurança. Os logs históricos podem ter sido coletados com mensagens de obsolescência ou outras severidades relevantes excluídas. Mesmo uma auditoria com E_ALL cobre apenas as rotas e os estados da aplicação que foram exercitados. Uma falha também pode ocorrer antes que o PHP registre a mensagem. Os logs devem complementar uma auditoria estática da configuração e do fluxo da aplicação.

Importante: Siga também as recomendações de correção do relatório relacionado: CVE-2026-109618.

Impacto e confiabilidade

O impacto confirmado é o uso de memória do heap após sua liberação, sem autenticação, a divulgação de memória e a execução remota de código com os privilégios do processo trabalhador da aplicação web. O caminho do backtrace de erro fatal tem RCE confirmada contra o mod_php. A RCE relacionada ao ciclo de vida da sessão foi validada contra mod_php e FPM. As demais famílias de estados só estabelecem impacto de segurança depois que um produtor e um consumidor concretos são demonstrados.

As requisições do produtor e do consumidor precisam alcançar o mesmo processo trabalhador. A existência de vários processos trabalhadores e a reciclagem deles reduzem a probabilidade de sucesso por requisição, mas não eliminam a condição.