php2shell: 7 mitigações até atualizar o PHP
Em 21 de outubro de 2026, duas vulnerabilidades de execução remota de código no PHP vieram a público: o php2shell, CVE-2026-109618 e CVE-2026-109619, ambas com CVSS v4.0 9.2.
A correção de verdade é atualizar. Só que nem todo ambiente consegue atualizar hoje, seja por testes, seja por migração pendente. Um servidor de produção ainda no PHP 7.4, que não recebe correção oficial nenhuma, é o caso mais difícil.
Este post reúne 7 mitigações para aplicar antes da atualização, com o comunicado do php.net como fonte primária. Para cada uma, também mostro o que ela não cobre.

O que o php2shell permite
Segundo o php.net, o problema atinge “todas as instalações PHP que atendem requisições web”: PHP-FPM, Apache com mod_php, php-cgi, FrankenPHP e LiteSpeed. O risco é maior no mod_php, onde há um exploit demonstrado em que uma requisição sem autenticação dispara o ataque antes de qualquer código da aplicação rodar.
| CVE | O que é (resumo do php.net) |
|---|---|
| CVE-2026-109618 | O tratamento de falta de memória pode interromper o PHP com os dados internos da requisição só parcialmente inicializados. A limpeza da requisição pode tratar esses dados inválidos como válidos |
| CVE-2026-109619 | Um estado interno da requisição pode sobreviver depois que a memória dele foi liberada. Uma requisição seguinte no mesmo worker pode usar esse estado antigo antes da inicialização |
A Sansec descreve dois ganhos para o atacante: execução de código sem login e um vazamento de memória parecido com o Heartbleed, que pode expor senhas, tokens de sessão e chaves de API de requisições de outros usuários. A Sansec também aponta que todos os gatilhos conhecidos são requisições multipart/form-data.
A conclusão oficial: não existe mitigação completa
O php.net é direto:
There are no complete mitigations, updating is the only safe choice.
As sete medidas abaixo servem para ganhar tempo até a atualização. Aplicar todas não deixa o servidor seguro.
| Versão | Situação |
|---|---|
| 8.2 / 8.3 / 8.4 / 8.5 | Corrigidas (8.2.35 / 8.3.36 / 8.4.27 / 8.5.12) |
| 7.4 / 8.0 / 8.1 | Sem novas versões do projeto PHP. Só backports mínimos do código-fonte |
7 mitigações antes de atualizar
1. Desligar session.upload_progress.enabled (CVE-2026-109619)
O php.net indica esta medida para os caminhos de sessão confirmados da CVE-2026-109619.
; /etc/php.d/99-php2shell-mitigation.ini
session.upload_progress.enabled = 0
No pool do FPM:
; /etc/php-fpm.d/www.conf
php_admin_flag[session.upload_progress.enabled] = off
Dois cuidados:
ini_set()e o.user.inido FPM não funcionam. Use o php.ini, o pool ou a configuração do servidor, que valem antes do parsing da requisição- Reinicie todos os workers
O php.net deixa claro que isso bloqueia só os caminhos confirmados, não outras variantes.
2. Definir memory_limit como -1 (CVE-2026-109618)
A CVE-2026-109618 começa na falha do limite de memória do PHP. O php.net sugere remover esse limite de um jeito que a aplicação não consiga sobrescrever:
; pool do FPM
php_admin_value[memory_limit] = -1
Confira se nenhum outro pool, diretório ou configuração da aplicação volta a colocar um limite finito. O php.net observa que uma falha de alocação por baixo ainda pode chegar ao caminho vulnerável.
Sem limite, uma única requisição pode consumir toda a memória. Use junto com a medida 4.
3. Baixar o post_max_size e limitar o tamanho na borda (CVE-2026-109618)
Os valores do php.net:
| Aplicação | post_max_size |
|---|---|
| Sem upload de arquivos | 8M |
| Com upload | 32M |
Mantenha o upload_max_filesize abaixo do post_max_size.
O php.net também recomenda limitar, no servidor web ou no proxy, o tamanho decodificado da requisição, a quantidade de campos, a quantidade de partes do multipart, a quantidade de cabeçalhos e a profundidade de aninhamento, inclusive em requisições sem Content-Length utilizável. No nginx, o client_max_body_size pelo menos limita o corpo inteiro.
4. Colocar um limite de memória por container ou cgroup
A medida 2 tira o limite do próprio PHP, então é preciso uma barreira de disponibilidade por fora. O php.net recomenda uma política que mate só o worker afetado.
Com systemd:
# systemctl edit php-fpm
[Service]
MemoryMax=4G
OOMPolicy=continue
Sem o OOMPolicy, um único worker morto pelo OOM killer pode derrubar o serviço inteiro, porque o padrão do systemd é stop.
Não use RLIMIT_AS nem RLIMIT_DATA no lugar. O php.net avisa que eles podem provocar a própria falha de alocação vulnerável.
5. Bloquear multipart na borda
Segundo a Sansec, todos os gatilhos conhecidos são multipart/form-data. Páginas que nunca recebem upload não têm motivo para aceitar multipart. Bloquear no WAF ou no proxy reverso corta caminhos antes de chegarem ao PHP.

No AWS WAF, bloqueie requisições cujo Content-Type contenha multipart e cujo Host não seja o que precisa de upload:
{
"Name": "block-multipart-except-upload-host",
"Priority": 0,
"Action": { "Block": {} },
"Statement": {
"AndStatement": {
"Statements": [
{ "ByteMatchStatement": {
"SearchString": "multipart",
"FieldToMatch": { "SingleHeader": { "Name": "content-type" } },
"TextTransformations": [{ "Priority": 0, "Type": "LOWERCASE" }],
"PositionalConstraint": "CONTAINS" } },
{ "NotStatement": { "Statement": { "ByteMatchStatement": {
"SearchString": "admin.example.com",
"FieldToMatch": { "SingleHeader": { "Name": "host" } },
"TextTransformations": [{ "Priority": 0, "Type": "LOWERCASE" }],
"PositionalConstraint": "EXACTLY" } } } }
]
}
},
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "block-multipart-except-upload-host"
}
}
Antes, verifique três coisas:
- Quais funções usam multipart de verdade. Procure
FormData/$_FILESno código e os destinos de POST nos logs de acesso - Se a exceção pode ser contornada. O cabeçalho Host pode ser falsificado. Garanta que os caminhos PHP do host liberado estejam protegidos, por exemplo com lista de IPs. Se vários sites dividem um único Web ACL, basta mandar “o host de admin do outro site” para outro ALB para passar. Use um ACL por ALB
- Se a regra bloqueia mesmo. Depois de mudar para Block, envie um GET sem corpo com Content-Type multipart para um arquivo estático. O PHP não processa corpo de GET, então o teste é seguro em produção
curl -s -o /dev/null -w '%{http_code}\n' \
-H 'Content-Type: multipart/form-data; boundary=x' \
https://www.example.com/robots.txt
# 403 indica que a regra funciona
O multipart para o host liberado continua passando, então as medidas 1 a 4 precisam cobrir esse caso.
6. Ajuste extra no PHP 8.5 e diagnósticos antecipados (CVE-2026-109619)
O php.net lista mais medidas para a CVE-2026-109619:
- No PHP 8.5, defina
fatal_error_backtraces=0e reinicie todos os workers. Isso bloqueia só o caminho confirmado de backtrace de erro fatal - Remova configurações inválidas ou obsoletas aplicadas por requisição que emitem diagnósticos antes da inicialização. Esconder com
error_reporting,display_errorsou@não resolve - Corrija erros fatais e timeouts de execução que um atacante consiga provocar, incluindo falhas em callbacks, no processamento de upload ou importação e na limpeza
7. No PHP 7.4 / 8.0 / 8.1, aplicar os backports ou a atualização do Remi
Para as versões sem release oficial, o php.net publicou backports mínimos do código-fonte (7.4 / 8.0 / 8.1) para quem compila o PHP.
Muitos servidores com PHP 7.4 via RPM usam o repositório do Remi (rpms.remirepo.net). O comunicado do php.net dizia que o repositório não teria atualização naquele dia, mas o 7.4.33-31 saiu em 21/10 mesmo assim.
# pacotes de módulo (para SCL use 'php74-php*')
sudo dnf update 'php*'
# confirme pelo changelog
rpm -q --changelog php-common | head -5
A atualização está instalada se o changelog mostrar:
- Mitigation fix Avoid state corruption via bailout
CVE-2026-109618, CVE-2026-109619
O texto diz “Mitigation fix” (correção de mitigação). Só pelo changelog não dá para saber se equivale às correções do 8.x.
Aplicar um servidor por vez, sem downtime
Mudar o ini exige reload do php-fpm. Com o process_control_timeout no padrão 0, as requisições em andamento recebem 502 no momento do reload.
Atrás de um balanceador, tire um servidor por vez:
aws elbv2 deregister-targets --target-group-arn "$TG_ARN" --targets Id="$ID"
aws elbv2 wait target-deregistered --target-group-arn "$TG_ARN" --targets Id="$ID"
# no servidor: coloque a config, faça reload e confira o valor
# systemctl reload php-fpm
# php-fpm -i | grep session.upload_progress.enabled
aws elbv2 register-targets --target-group-arn "$TG_ARN" --targets Id="$ID"
aws elbv2 wait target-in-service --target-group-arn "$TG_ARN" --targets Id="$ID"
O php-fpm -i mostra os valores do php.ini. Para valores definidos no pool com php_admin_value, confira a saída do php-fpm -tt.
Com Auto Scaling, o que foi aplicado à mão não chega às instâncias novas. Atualize também o launch template ou a AMI.
O que vi no meu ambiente de produção
Na noite do anúncio, apliquei a medida 1 (session.upload_progress.enabled=0) e a medida 5 (bloqueio de multipart) em um ambiente de produção com PHP 7.4 que eu mantenho, um servidor por vez e sem downtime.
Logo depois que o WAF entrou, chegaram POSTs multipart: 6 IPs de 5 países, todos com os mesmos dois tamanhos de payload. O User-Agent era sempre Python aiohttp.
| Ataque | Resultado |
|---|---|
| O primeiro (para o domínio apex) | O redirect 301 de apex para www transformou o POST em GET, e o payload não chegou |
| Os 5 IPs seguintes | Bloqueados pela regra de multipart do WAF |

No que verifiquei, não houve sinal de invasão. A linha do tempo completa está no meu relato no Qiita (em japonês).
Onde a documentação e a realidade divergiram
São coisas que só se descobrem na prática.
| Expectativa | O que aconteceu |
|---|---|
| php.net: o Remi não teria atualização naquele dia | O 7.4.33-31 saiu no mesmo dia. Estava no repositório antes de qualquer post no blog do Remi |
| Mudar a regra do WAF de Count para Block faz bloquear | Devolver a saída do get-web-acl direto para o update-web-acl codificou a string de busca duas vezes em base64. A regra passou a procurar bXVsdGlwYXJ0 em vez de multipart, sem nenhum erro |
| Somar as regras gerenciadas é uma camada extra segura | O NoUserAgent_Header do CommonRuleSet bateu em muito tráfego legítimo de app nativo e WebSocket. Bloquear de cara derrubaria o app |
Depois de qualquer mudança no WAF, decodifique a string de busca e confirme que o teste com curl devolve 403:
aws wafv2 get-web-acl --scope REGIONAL --name "$ACL_NAME" --id "$ACL_ID" \
| jq -r '.WebACL.Rules[0].Statement.AndStatement.Statements[0]
.ByteMatchStatement.SearchString' | base64 -d
# deve imprimir: multipart
O que continua aberto, e como checar invasão
O que sobra mesmo com as sete medidas:
| Continua aberto | Motivo |
|---|---|
| Variantes fora dos caminhos confirmados | O php.net diz que as medidas 1 e 6 cobrem só os caminhos confirmados |
| Páginas que aceitam multipart legitimamente | O host liberado na medida 5 continua recebendo |
| Falhas de alocação fora do limite do PHP | Ainda podem chegar ao caminho vulnerável com a medida 2 |
| Servidor que já tinha sido invadido | As mitigações fecham a porta. Web shell ou cron plantados continuam lá |
Para a última linha, verifique sinais de invasão:

- Aumentaram os crashes de worker do php-fpm (signal 11 e parecidos)?
- Há erros de limite de memória ou de timeout de execução?
- Há shells ou processos filhos inesperados sob o php-fpm?
- Os arquivos PHP e binários modificados recentemente são todos dos seus próprios deploys?
- Houve mudança no cron ou conexões de saída desconhecidas?
Ao contar linhas de log por dia, não ordene datas no formato 21-Oct-2026 como texto. Elas não ficam em ordem de mês, e o tail pode cortar o mês mais recente. Filtre pelo mês antes.
Depois de atualizar, você pode remover as mitigações. Antes disso, confirme a versão corrigida pela string de versão ou pelo changelog.
Referências
Este artigo foi útil?