Imagem Docker PHP Vulnerável: Rebuild Após o PHP2Shell

docker pull não corrige o PHP2Shell. Veja como reconstruir imagens php:8.x e FrankenPHP e o que mudar no CI/CD para o patch chegar à produção.

por Cleverson Gouvêa

Terminal mostrando o rebuild de uma imagem Docker PHP com a versão 8.4.27 corrigida contra o PHP2Shell

Uma imagem docker php vulnerável não se corrige sozinha quando o PHP lança o patch. O container em produção continua rodando o binário com que foi criado, mesmo depois que o Docker Hub publicou a versão nova. Neste guia eu mostro como achar cada imagem afetada pelo PHP2Shell, como fazer o rebuild das imagens php:8.x e do FrankenPHP do jeito certo, o que mudar no pipeline de CI/CD e por que o limite de memória do container não fecha a falha.

TL;DR

  • O PHP2Shell são duas falhas críticas (CVE-2026-109618 e CVE-2026-109619, CVSS 4.0 = 9.2) que permitem execução remota de código sem login. A correção saiu em 21/10/2026 nas versões 8.5.12, 8.4.27, 8.3.36 e 8.2.35.
  • As imagens oficiais php:8.x com o patch já estão no Docker Hub desde 21/10. O FrankenPHP corrigiu na v1.13.2, do mesmo dia.
  • docker pull não corrige nada sozinho. É preciso rebuild com base nova, push e recriação de cada container.
  • Tag fixa antiga, digest fixado e cache de build seguram a versão velha sem avisar.
  • Limite de memória do container (cgroup) protege a disponibilidade, não fecha a falha. A outra CVE nem depende de memória.

Por que uma imagem docker php vulnerável continua vulnerável depois do patch

Em servidor tradicional, o apt upgrade troca o pacote e o restart do PHP-FPM resolve. Em container, a lógica é outra: a imagem é imutável. O PHP que roda ali dentro foi compilado e congelado no momento do build.

Isso cria três camadas que precisam ser atualizadas, nesta ordem:

  1. A imagem base (php:8.4-fpm, php:8.3-apache, dunglas/frankenphp) precisa ter a versão corrigida.
  2. A imagem da sua aplicação, que herda da base via FROM, precisa ser reconstruída e enviada ao registry.
  3. Os containers em execução precisam ser recriados a partir da imagem nova.

Pular qualquer camada deixa uma imagem docker php vulnerável em produção, com cara de atualizada. O cenário mais comum que vejo é o time rodar docker pull no servidor, ver "Downloaded newer image" e encerrar o chamado. A base nova está no disco, mas o container antigo segue de pé, e a imagem da aplicação nem foi reconstruída.

Trabalho com PHP desde 2008 e uso containers todos os dias, do ambiente de desenvolvimento com DDEV aos serviços que entregamos em desenvolvimento de sistemas sob medida. A diferença entre um patch aplicado e um patch "baixado" é sempre essa: o processo que de fato atende as requisições.

PHP2Shell em dois minutos: o que mudou entre 17 e 23 de outubro

O PHP2Shell é o nome dado a duas falhas no núcleo do PHP. O aviso oficial do php.net diz que estão em risco "todas as instalações PHP que atendem requisições web", citando PHP-FPM, Apache com mod_php, php-cgi, FrankenPHP e LiteSpeed. O maior risco, segundo o próprio PHP, é o mod_php: lá o ataque dispara antes de qualquer código da aplicação.

  • CVE-2026-109618 (corrupção por falta de memória): o PHP fica sem memória no meio da montagem da requisição, e a limpeza trata dados pela metade como válidos.
  • CVE-2026-109619 (estado global obsoleto, um use-after-free): um estado interno sobrevive depois que a memória foi liberada, e a próxima requisição no mesmo worker usa esse resto.

A linha do tempo explica a pressa. Segundo o FAQ da Tenable, a Amazon viu uma tentativa de exploração em 17/10. Os detalhes vazaram em 20/10 e o patch saiu em 21/10. A Palo Alto Networks identificou mais de 22,6 milhões de instâncias expostas, conforme a TechTarget. Em 23/10, o PHP publicou um alerta extra (CVE-2026-115560) sobre um vetor improvável ligado ao upload progress das sessões, que será corrigido na próxima release regular.

Ramo do PHP Situação no PHP Imagem oficial php no Docker Hub
8.5 Corrigido na 8.5.12 Atualizada em 21/10/2026
8.4 Corrigido na 8.4.27 Atualizada em 21/10/2026
8.3 Corrigido na 8.3.36 Atualizada em 21/10/2026
8.2 Corrigido na 8.2.35 (fim de vida em 31/12/2026) Atualizada em 21/10/2026
7.4, 8.0 e 8.1 Só backports mínimos de código-fonte Sem imagem nova: a tag 8.1-fpm não recebe push desde 31/12/2025
5.4 a 7.3 Sem correção Abandonadas (a 7.4-fpm parou em 15/11/2022)

As datas da última coluna conferi direto na API do Docker Hub e no repositório da imagem oficial. A tag 8.4.27-fpm recebeu o primeiro push em 21/10, às 16h43 (UTC).

Como descobrir quais imagens estão vulneráveis

Antes de reconstruir, faça o inventário de cada imagem docker php vulnerável que você tem. Em empresa com mais de um projeto, sempre aparece um container esquecido: o painel antigo, o worker de fila, o ambiente de homologação que ficou exposto.

Pergunte ao container, não ao host

O PHP do host não diz nada sobre o PHP do container, e uma imagem docker php vulnerável pode estar rodando num servidor cujo php -v mostra 8.4.27. Também não confie só no php -v da CLI: a Datadog alerta que a instalação de linha de comando pode ser diferente do runtime que atende a web. Rode a verificação dentro de cada serviço:

# versão do PHP em cada container em execução
for c in $(docker ps -q); do
  echo "$(docker inspect --format '{{.Name}}' $c): $(docker exec $c php -r 'echo PHP_VERSION;' 2>/dev/null || echo 'sem php')"
done

# imagem que o container usa x imagem mais nova no disco
docker inspect --format '{{.Image}}' meu-container
docker image ls --digests php

Olhe o registry, não só os servidores

As imagens oficiais compilam o PHP a partir do código-fonte e gravam a versão na variável PHP_VERSION. Dá para auditar sem subir nada:

docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' registry.exemplo.com/app:prod | grep PHP_VERSION

Um detalhe que pega muita gente: como o PHP da imagem oficial não vem de pacote do Debian ou do Alpine, scanners que só leem o banco de pacotes do sistema podem não enxergá-lo. A própria Datadog recomenda inventário separado para runtimes compilados. Se a sua imagem docker php vulnerável não aparece no relatório do scanner, isso não prova que ela está segura.

O que entra na lista

  • Containers web (FPM, Apache, FrankenPHP).
  • Workers de fila, cron e consumidores que usam a mesma imagem.
  • Imagens de homologação e de preview geradas pelo CI.
  • Imagens de terceiros (painéis, ferramentas internas) que embutem PHP. Esta costuma ser a imagem docker php vulnerável que ninguém lembra de reconstruir.

Rebuild das imagens php:8.x: o passo a passo que funciona

Com o inventário pronto, corrigir uma imagem docker php vulnerável segue uma sequência curta. O que muda de projeto para projeto é como o FROM está escrito.

Tag flutuante, tag fixa ou digest

Como está o FROM Exemplo Recebe o patch sozinho? O que fazer
Tag de ramo php:8.4-fpm Sim, no próximo build com --pull Rebuild com --pull
Tag exata php:8.4.26-fpm Não Editar para 8.4.27-fpm e reconstruir
Digest php:8.4-fpm@sha256:... Não Atualizar o digest e reconstruir
Ramo sem suporte php:8.1-fpm Nunca Migrar a aplicação para 8.2+

Tag exata e digest são boas práticas de reprodutibilidade. O preço é exatamente este: numa correção urgente, alguém precisa editar o arquivo. Se o seu projeto usa digest, a imagem docker php vulnerável fica travada até que um humano (ou um bot de dependências) mude a linha.

Os comandos

# 1. reconstruir com base nova (e sem cache, se o cache segurar a versão velha)
docker build --pull --no-cache -t registry.exemplo.com/app:2026-10-24 .

# 2. conferir a versão ANTES de publicar
docker run --rm registry.exemplo.com/app:2026-10-24 php -r 'echo PHP_VERSION, PHP_EOL;'

# 3. publicar e recriar os containers
docker push registry.exemplo.com/app:2026-10-24
docker compose pull && docker compose up -d --force-recreate

# 4. validar o que está rodando
docker compose exec web php -r 'echo PHP_VERSION;'

O --no-cache importa. Sem ele, uma camada em cache pode reaproveitar a base antiga, e o build termina "verde" com a mesma imagem docker php vulnerável de antes. Recriar o container reinicia todos os workers do PHP. Os volumes montados continuam intactos.

Variantes: atualize todas

Cada variante é uma imagem separada: -fpm, -apache, -cli, -zts, Debian (bookworm, trixie) e -alpine. Comece pelas -apache, que usam mod_php, o cenário que o PHP considera de maior risco. E não publique a porta do FPM (9000) para fora da rede interna: FastCGI não foi feito para ficar exposto.

FrankenPHP: imagem Docker e binário estático

O FrankenPHP merece um capítulo à parte, porque ele vem em dois formatos e cada um se atualiza de um jeito.

Imagem Docker

A release v1.13.2, de 21/10/2026, trouxe as imagens com PHP 8.2.35, 8.3.36, 8.4.27 e 8.5.12, citando explicitamente o PHP2Shell. Dois dias depois saiu a v1.13.3, que corrige travamentos (SIGILL) dos binários estáticos musl em CPUs mais antigas. Uma imagem docker php vulnerável baseada em FrankenPHP se corrige pelo mesmo caminho das oficiais. Se o seu Dockerfile usa dunglas/frankenphp:1-php8.4 ou 1.13-php8.4, o rebuild com --pull resolve. Se usa uma tag fixa anterior, como 1.13.1-php8.4, edite para 1.13.3 ou superior.

Binário estático

Aqui mora a armadilha. O binário estático do FrankenPHP embute o PHP dentro do executável. Atualizar o sistema operacional não muda nada. Atualizar o pacote php da distro também não. A única correção é trocar o binário por uma versão que traga o PHP 8.5.12 (a v1.13.2 ou posterior) e reiniciar o serviço.

Quem empacota a aplicação inteira num único executável (o recurso de "embed" do FrankenPHP) precisa refazer esse build também. É exatamente o caso que a Datadog descreve como "aplicações que trazem o próprio interpretador PHP".

Modo worker

No modo worker, o FrankenPHP mantém a aplicação carregada na memória entre requisições. A CVE-2026-109619 explora justamente estado que sobrevive de uma requisição para a próxima no mesmo worker. Não há atalho: atualize e reinicie todos os workers.

Pipelines CI/CD: como não ser pego de novo

O PHP2Shell mostrou o tamanho da janela entre "patch publicado" e "patch rodando". Em pipeline mal desenhado, uma imagem docker php vulnerável fica semanas em produção. Em pipeline bom, é de horas.

O que configurar

  1. Rebuild agendado. Um job semanal (e um gatilho manual) que reconstrói a imagem com --pull, mesmo sem commit novo. Base atualizada não gera commit, então o pipeline que só roda em push nunca pega o patch.
  2. Bot de dependências para o FROM. Renovate e Dependabot sabem abrir PR atualizando tag e digest de imagem. Você mantém a reprodutibilidade e ganha velocidade.
  3. Portão de versão mínima. Um passo que falha o build se o PHP estiver abaixo do mínimo corrigido:
docker run --rm "$IMAGE" php -r 'exit(version_compare(PHP_VERSION, "8.4.27", ">=") ? 0 : 1);' \
  || { echo "PHP abaixo da versão corrigida"; exit 1; }
  1. Tag por data ou por commit, nunca só latest. Assim o rollback aponta para uma imagem conhecida, e o registry mostra o que está em produção.
  2. Escaneamento da imagem final, com o cuidado já citado: confira a versão do PHP pela variável PHP_VERSION, não só pelo banco de pacotes.

Quanto custa

Manter cada imagem docker php vulnerável sob controle custa pouco. O custo direto é pequeno: minutos de CI por semana e algum espaço a mais no registry. O custo real é de engenharia, de algumas horas a poucos dias para padronizar Dockerfiles, criar o job e testar o rollback. Compare com o custo de uma invasão sem autenticação num servidor exposto.

Para quem roda Moodle em container, a lógica é a mesma, com um cuidado extra: confira a matriz de versões do PHP de cada release do Moodle antes de trocar de ramo. Os requisitos de servidor para Moodle ajudam nessa conta.

Por que o limite de memória do container (cgroup) não é mitigação completa

Este é o ponto que mais vejo confundido. Como uma das falhas envolve falta de memória, parece lógico que um --memory=512m no container resolva. Não resolve. Uma imagem docker php vulnerável com limite de memória continua sendo vulnerável.

O que o PHP realmente recomenda

A mitigação oficial para a CVE-2026-109618 é memory_limit=-1, aplicado de forma que a aplicação não possa sobrescrever (php_admin_value no pool do FPM). Isso remove o teto controlável do PHP. Mas o próprio aviso admite que "uma falha de alocação subjacente ainda pode chegar ao caminho vulnerável".

Como memory_limit=-1 deixa um script consumir toda a RAM, o PHP sugere usar um limite de container ou cgroup "como barreira de disponibilidade", de preferência com uma política que mate só o worker afetado. A palavra é disponibilidade. Não é segurança.

Quatro motivos para não confiar no cgroup

  1. A CVE-2026-109619 não tem nada a ver com memória. Ela explora estado obsoleto entre requisições. Nenhum limite de RAM interfere nela.
  2. O limite protege o host, não o PHP. Quando o container estoura o cgroup, o kernel mata um processo. Isso evita que o servidor inteiro caia, mas não corrige a corrupção que o atacante busca.
  3. RLIMIT_AS e RLIMIT_DATA pioram. O PHP pede explicitamente para não usar esses limites de processo no lugar do cgroup, porque eles podem provocar justamente a falha de alocação vulnerável. Cuidado com ulimit -v em scripts de entrypoint: ele aplica exatamente um RLIMIT_AS.
  4. Matar o processo errado derruba o serviço. Se o OOM killer acerta o processo master do FPM, o container inteiro reinicia. Repetido em loop, isso vira negação de serviço provocada pelo próprio atacante.

Como montar a defesa em camadas, enquanto atualiza

Camada Configuração Papel
PHP memory_limit=-1 via php_admin_value Remove o gatilho previsível da 109618
PHP session.upload_progress.enabled=0 Fecha os caminhos confirmados da 109619
PHP 8.5 fatal_error_backtraces=0 Fecha o caminho do backtrace fatal
Container --memory / limits.memory no Kubernetes Barreira de disponibilidade
Proxy/WAF limite de tamanho, campos e partes multipart Reduz payloads conhecidos

A Datadog resume do jeito certo: essas mudanças "reduzem a exposição enquanto você atualiza, mas nenhuma corrige as vulnerabilidades". Se quiser reforçar a borda, o guia de como proteger servidor web contra ataques cobre WAF, fail2ban e rate limit.

Quando NÃO fazer: armadilhas que custam caro

Em correção de emergência, o erro mais comum é a pressa no lugar errado. Fuja destes:

  • Não use tag de release candidate. Tags como 8.5.12RC1 não são a versão final. Use só tags estáveis.
  • Não rode apt upgrade dentro do container em execução. Além de não trocar o PHP compilado da imagem oficial, a mudança some no próximo restart.
  • Não confie em ini_set() ou .user.ini para mitigar. O PHP avisa que eles entram tarde demais, depois da leitura da requisição.
  • Não reconstrua uma imagem 8.1 esperando correção. Ela não vem. Para 7.4, 8.0 e 8.1, o PHP só publicou backports mínimos de código-fonte; a saída realista é migrar para 8.2+, de preferência 8.3 ou 8.4, já que o 8.2 sai de suporte em 31/12/2026.
  • Não faça tudo direto em produção. Trocar a imagem docker php vulnerável por uma de outro ramo sem teste troca um problema por outro. Uma troca de ramo (8.1 para 8.4, por exemplo) pode quebrar extensões e código legado. Patch dentro do mesmo ramo (8.4.26 para 8.4.27) é de baixo risco. Mudança de ramo exige homologação.
  • Não esqueça o hardening do host. O container roda sobre um Linux que também precisa estar em dia; o checklist de hardening de servidor Linux cobre essa camada.

E depois de atualizar, procure sinais de que o ataque aconteceu antes do patch. A Datadog lista indícios como erros de memória esgotada com "Unknown on line 0" no log, segfaults (sinal 11) e, o mais direto, um worker web abrindo um shell. Se aparecer qualquer um desses, trate como incidente, não como manutenção.

Como a Agathas Web resolve isso

Se você tem uma ou mais aplicações PHP em container e não tem certeza de que nenhuma imagem docker php vulnerável sobrou em produção, é exatamente esse tipo de trabalho que entregamos no serviço de desenvolvimento e sustentação de sistemas.

O que está incluso

  • Inventário completo: containers em execução, registry, workers, cron e ambientes de homologação, com a versão real do PHP de cada um.
  • Rebuild e publicação: atualização do FROM, build sem cache, push, recriação dos containers e validação da versão em execução.
  • FrankenPHP e binários estáticos: troca do binário ou da imagem, inclusive em aplicações empacotadas com o PHP embutido.
  • Mitigações temporárias bem aplicadas enquanto a migração de ramo não termina, no nível certo (pool do FPM, não .user.ini).
  • Pipeline à prova da próxima falha: rebuild agendado, bot de dependências, portão de versão mínima e rollback testado.
  • Migração de PHP legado (7.4, 8.0, 8.1) para um ramo suportado, com homologação antes da produção.

Como funciona na prática

Começamos com um diagnóstico curto: você nos dá acesso de leitura ao registry e aos servidores, e devolvemos a lista do que está vulnerável e a ordem de correção. Depois executamos o rebuild em janela combinada, com rollback pronto. A mesma equipe cuida de Moodle, de servidores Linux, de hospedagem gerenciada e de integrações com a API oficial do WhatsApp, então a aplicação inteira fica sob o mesmo olhar, não só o container.

Como contratar

Fale com a gente pela página de desenvolvimento de sistemas da Agathas Web, descreva a stack (quantos projetos, quais imagens, onde rodam) e respondemos com escopo e prazo. Para urgência ligada ao PHP2Shell, diga isso logo na primeira mensagem.

Conclusão: patch só vale quando chega ao processo

A lição do PHP2Shell para quem usa Docker é simples. O patch saiu em 21/10, as imagens oficiais e o FrankenPHP foram atualizados no mesmo dia, mas nada disso protege um container que não foi reconstruído e recriado. Inventário, rebuild com --pull --no-cache, push, recriação e validação da versão: essa é a sequência. Limite de memória do container ajuda a manter o serviço de pé, mas não fecha a falha.

Se você quer sair desta semana com a certeza de que nenhuma imagem docker php vulnerável sobrou, e com um pipeline que pegue a próxima falha em horas, fale com a Agathas Web.

Perguntas frequentes

Só rodar docker pull resolve o PHP2Shell no meu container?

Não. O docker pull baixa a imagem base nova para o disco, mas o container em execução continua usando a imagem com que foi criado. Você precisa reconstruir a imagem da sua aplicação com docker build --pull (e --no-cache, se o cache segurar a camada antiga), publicar no registry e recriar o container com docker compose up -d --force-recreate. Depois, confirme dentro do container com php -r 'echo PHP_VERSION;' que a versão é 8.5.12, 8.4.27, 8.3.36 ou 8.2.35, conforme o ramo.

A imagem oficial php:8.1 vai receber correção do PHP2Shell?

Não. O PHP 8.1 saiu de suporte, e a tag php:8.1-fpm no Docker Hub não recebe push desde 31/12/2025. Para 7.4, 8.0 e 8.1, o time do PHP publicou apenas backports mínimos de código-fonte, pensados para quem compila o PHP por conta própria ou não tem um fornecedor de suporte estendido. Na prática, a saída é migrar a aplicação para um ramo suportado, de preferência 8.3 ou 8.4, com homologação antes de ir para produção, já que o 8.2 sai de suporte em 31/12/2026.

Colocar limite de memória no container protege contra o PHP2Shell?

Só em parte, e na dimensão errada. O próprio PHP recomenda o limite de container ou cgroup como barreira de disponibilidade, para que um memory_limit=-1 não deixe um script consumir toda a RAM do host. Ele não corrige a corrupção explorada na CVE-2026-109618 e não tem efeito nenhum sobre a CVE-2026-109619, que explora estado obsoleto entre requisições. Também não use RLIMIT_AS ou RLIMIT_DATA (como ulimit -v), porque podem provocar a falha de alocação vulnerável. A única correção completa é atualizar o PHP.

Como corrijo o PHP2Shell no FrankenPHP?

Depende do formato. Na imagem Docker, a release v1.13.2 do FrankenPHP, de 21/10/2026, trouxe o PHP 8.2.35, 8.3.36, 8.4.27 e 8.5.12; basta reconstruir com --pull se você usa tag de ramo, ou editar a tag fixa para 1.13.3 ou superior. No binário estático, o PHP vem embutido no executável, então atualizar o sistema operacional não adianta: troque o binário por uma versão corrigida e reinicie o serviço. Aplicações empacotadas com o PHP embutido precisam de um build novo.

Com que frequência devo reconstruir minhas imagens Docker PHP?

Além de reconstruir sempre que sai um patch de segurança, vale ter um rebuild agendado pelo menos semanal, com --pull, mesmo sem mudança no código. Atualização da imagem base não gera commit, então um pipeline que só roda em push nunca pega o patch. Combine isso com um bot de dependências (Renovate ou Dependabot) para tags e digests do FROM e com um passo no CI que reprove o build se a versão do PHP estiver abaixo do mínimo corrigido.