defense php2shell defense playbook

Playbook de Defesa em Produção: Regras Nginx, Virtual Patches WAF e Sandboxing PHP-FPM

php2shell.run Research Lab •

Playbook de Defesa em Produção: Regras Nginx, Virtual Patches WAF e Sandboxing PHP-FPM

Verificado em 23 de outubro de 2026.

<time datetime="2026-10-23">Verificado em 23 de outubro de 2026</time>

Este playbook descreve medidas de proteção aplicáveis sem downtime contra CVE-2026-109618 e CVE-2026-109619 (PHP2Shell). A ordem é deliberada: o patch vem primeiro. WAF, Nginx e sandboxing preenchem o tempo até o rollout e reduzem o impacto caso um ataque ainda tenha sucesso. Cada camada é deployável independentemente e acompanhada de plano de rollback.

1. Arquitetura de defesa em profundidade

Tier 0: CDN/Edge WAF (Cloudflare etc.) — rate limits, assinaturas conhecidas, bloqueio geográfico.

Tier 1: perímetro Nginx — separar caminhos de upload, multipart 415/403, limites de body/buffer, timeouts.

Tier 2: ModSecurity/Coraza virtual patch — inspeção de campos multipart, score de anomalia.

Tier 3: configuração PHP-FPM — session.upload_progress Off, pm.max_requests, limiares de restart.

Tier 4: sandbox systemd + Seccomp — ProtectSystem, PrivateTmp, NoNewPrivileges, bloqueio de execve (quando fachamente viável).

As camadas cobrem modos de falha diferentes. Tier 1 pode ser contornado por codificação, Tier 2 por variantes de campo desconhecidas, Tier 3 por má configuração, Tier 4 por exceções necessárias. Nenhuma camada sozinha é suficiente.

2. Configurações prontas para uso

2.1 Tier 1: perímetro Nginx

client_max_body_size 8m;
client_body_buffer_size 128k;
client_body_timeout 15s;
client_header_timeout 10s;
send_timeout 20s;

location ~ ^/(api|health|static|assets)/ {
    if ($http_content_type ~* "multipart/form-data") { return 415; }
    client_max_body_size 256k;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    fastcgi_read_timeout 20s;
}

location ^~ /upload/ {
    client_max_body_size 64m;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}

O efeito mais importante não é a resposta 415 em si, mas a separação de caminhos com e sem upload. Isso reduz a superfície de ataque aos poucos endpoints que realmente aceitam arquivos.

2.2 Tier 2: ModSecurity / Coraza

SecRuleEngine On
SecRequestBodyAccess On
SecRequestBodyLimit 67108864
SecRequestBodyInMemoryLimit 131072

SecRule MULTIPART_FILENAME|ARGS_NAMES|REQUEST_HEADERS "@rx (?i)PHP_SESSION_UPLOAD_PROGRESS" \
  "id:1096181,phase:2,deny,status:403,log,msg:'PHP2Shell upload progress field'"

SecRule REQUEST_HEADERS:Content-Type "@rx (?i)multipart/form-data" \
  "id:1096182,phase:1,pass,nolog,chain"
  SecRule REQUEST_URI "@rx ^/(api|health|static|assets)/" \
    "t:lowercase,deny,status:415,msg:'multipart to non-upload path'"

Mantenha os IDs de regra também no Coraza. Isso facilita correlação no SIEM e torna relatórios comparáveis entre sites.

2.3 Tier 3: PHP-FPM

session.upload_progress.enabled = Off
session.upload_progress.cleanup = On
expose_php = Off

Pool:

php_admin_flag[session.upload_progress.enabled] = off
pm = dynamic
pm.max_children = 24
pm.max_requests = 300
emergency_restart_threshold = 5
emergency_restart_interval = 1m
request_terminate_timeout = 30s

2.4 Tier 4: sandbox systemd

NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/var/lib/php/sessions /var/www/uploads
PrivateTmp=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
MemoryDenyWriteExecute=yes

MemoryDenyWriteExecute=yes pode colidir com JIT ou certas extensões. Teste no staging antes de produção. Se não for possível, documente a exceção e compense com Seccomp e detecção em runtime.

3. Ordem de rollout e rollback

A ordem segue a relação impacto/risco.

  1. Tier 3 — imediato, sem downtime, maior impacto.
  2. Tier 1 — após verificar caminhos de upload e padrões de tráfego.
  3. Tier 2 — primeiro em modo log, depois mudar para deny.
  4. Tier 4 — apenas após validação em staging.

Backup e rollback:

sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak-$(date +%F)
sudo cp /etc/php/8.3/fpm/php.ini /etc/php/8.3/fpm/php.ini.bak-$(date +%F)
sudo cp /etc/php/8.3/fpm/php.ini.bak-2026-10-23 /etc/php/8.3/fpm/php.ini
sudo systemctl reload php8.3-fpm
sudo nginx -t && sudo systemctl reload nginx

4. Checklist de verificação

php -r 'echo ini_get("session.upload_progress.enabled"), PHP_EOL;'
php -v | head -1
sudo nginx -T 2>/dev/null | grep -A2 'multipart/form-data'
sudo tail -n 50 /var/log/modsec_audit.log
systemctl show php8.3-fpm -p ProtectSystem -p PrivateTmp -p NoNewPrivileges
curl -sS -F "file=@/etc/hostname" https://example.com/upload/ -o /dev/null -w '%{http_code}\n'

Execute a verificação de regressão após cada mudança. Um 415 em caminho legítimo de upload indica regra ampla demais.

5. FAQ operacional

P1: ProtectSystem=strict quebra arquivos de upload existentes? Não, desde que os diretórios de upload estejam em ReadWritePaths. Atenção a aplicações que usam /tmp: PrivateTmp=yes isola esse caminho, de modo que processos fora do serviço não veem os arquivos.

P2: Funciona em Docker/Kubernetes? Sim, com implementação diferente. No Kubernetes use readOnlyRootFilesystem, allowPrivilegeEscalation: false, seccompProfile e NetworkPolicy.

P3: O WAF bloqueia uploads multipart legítimos? O nome de campo PHP_SESSION_UPLOAD_PROGRESS não é usado por formulários modernos. Ainda assim, verifique aplicações legadas e ative a regra primeiro em modo log.

P4: Por quanto tempo manter as medidas? Até todos os runtimes PHP estarem corrigidos. Depois pode relaxar regras WAF, mas manter callback desativado se a função não for necessária.

P5: E shared hosting onde clientes definem seus próprios valores de php.ini? Force a configuração via php_admin_flag nos templates do painel e impeça overrides. Caso contrário, um único vHost pode anular a proteção.

6. Shared hosting e integração com painéis

Em ambientes de shared hosting com cPanel, Plesk ou CloudLinux, a php.ini global não basta, pois clientes podem definir seus próprios valores. Force a desativação via configuração do painel e verifique se php_admin_flag está presente nos templates de vHost ou pool. CloudLinux alt-php e EasyApache 4 têm caminhos diferentes; documente ambas as variantes e reteste após cada atualização de painel.

Comunicação com clientes também é importante. Muitos temem que a desativação quebre seus formulários de upload. Forneça um folheto curto explicando a diferença entre indicador de progresso e processamento de arquivo. Ofereça reativar após o patch se houver necessidade concreta — mas apenas após avaliação de risco. Assim evita-se tickets de suporte e impede-se que clientes contornem a medida por conta própria.

Para provedores com muitos tenants, um inventário central é essencial. Registre por vHost a versão do PHP, o nível de patch, o status de session.upload_progress e a SAPI usada. Sem esses dados, qualquer afirmação sobre risco agregado é especulação. Um CSV simples ou tabela de banco de dados basta como começo; mais importante que a ferramenta é a completude.

7. Tuning de WAF e falsos positivos

Ative novas regras primeiro em modo log e avalie por pelo menos 24 horas. Observe tenants legítimos com formulários de upload antigos que ainda usam o nome do campo. Esses casos exigem exceção estreita por caminho, não desativação global da regra. Documente cada exceção com data de expiração e responsável.

Um erro comum é combinar regra WAF muito rígida com falta de feedback ao desenvolvimento da aplicação. Quando um endpoint de upload passa a retornar 403, a equipe de desenvolvimento costuma procurar no lugar errado. Garanta mensagens de erro claras e documentação compartilhada dos IDs de regra. Isso reduz MTTR e evita que medidas de proteção sejam desligadas por frustração.

8. Conclusão

PHP2Shell não se resolve com um único produto. Configuração da engine, regras de perímetro, WAF e sandboxing abordam etapas diferentes da cadeia de ataque. O passo mais importante continua sendo o upgrade para 8.5.12, 8.4.27, 8.3.36 ou 8.2.35. Todo o resto é ponte — mas uma ponte que pode fazer a diferença entre um incidente e uma perda de dados.

9. Monitoramento e melhoria contínua

Um playbook de defesa só é eficaz se for continuamente validado. Estabeleça métricas: taxa de requisições bloqueadas por camada, falsos positivos por semana, tempo médio de detecção e tempo médio de resposta. Revise métricas mensalmente e ajuste regras. Execute exercícios de mesa (tabletop) trimestrais simulando cenários PHP2Shell com a equipe de resposta. Teste regras em staging após cada atualização de WAF ou Nginx. Documente mudanças e mantenha histórico auditável. A melhoria contínua transforma o playbook de documento estático em capacidade viva de defesa, capaz de acompanhar a evolução das ameaças e das próprias infraestruturas.