defense php2shell seccomp ebpf defense

Zero-Trust PHP-FPM: Hardening de Syscalls Linux com Seccomp-BPF e eBPF

php2shell.run Research Lab •

Zero-Trust PHP-FPM: Hardening de Syscalls Linux com Seccomp-BPF e eBPF

Verificado em 23 de outubro de 2026.

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

O último passo de qualquer cadeia PHP2Shell (CVE-2026-109618, CVE-2026-109619) é a execução de um processo. O patch é a melhor solução; quem não pode aplicá-lo imediatamente pode, porém, quebrar a cadeia no kernel. Seccomp-BPF filtra syscalls de forma irreversível, eBPF fornece a visibilidade necessária. Juntos, formam uma configuração em que uma corrupção de memória bem-sucedida não leva automaticamente a uma shell. Este documento descreve a implementação prática e os limites da abordagem.

1. Arquitetura de interrupção de syscalls

Worker PHP-FPM chama system("curl ... | sh"), que passa por glibc fork/clone/execve, então pelo filtro Seccomp-BPF no kernel, que retorna EPERM para execve/execveat. A cadeia termina.

Seccomp não é substituto de sandbox, mas a última barreira. Não previne a corrupção de memória, apenas a execução de processo subsequente. É exatamente a diferença entre "vazamento de dados" e "comprometimento total" no cenário PHP2Shell.

2. Perfil Seccomp para PHP-FPM

Um perfil de produção deve usar allowlist e proibir execve explicitamente.

{
  "defaultAction": "SCMP_ACT_ERRNO",
  "architectures": ["SCMP_ARCH_X86_64", "SCMP_ARCH_X86"],
  "syscalls": [
    { "names": ["read","write","close", "...", "exit_group"], "action": "SCMP_ACT_ALLOW" },
    { "names": ["execve","execveat"], "action": "SCMP_ACT_ERRNO", "errnoRet": 1 }
  ]
}

Dois detalhes importam. Primeiro, clone deve permanecer permitido se extensões iniciam threads; caso contrário, processamento de imagem ou drivers de banco podem quebrar. Segundo, fork deve ser permitido apenas se o worker realmente precisa de processos filhos. Para workloads puramente web, bloquear execve é o maior ganho com risco mínimo.

2.1 Teste com modo log

Antes de bloquear, colete. SCMP_ACT_LOG registra chamadas não permitidas sem impedi-las.

{ "defaultAction": "SCMP_ACT_LOG", "architectures": ["SCMP_ARCH_X86_64"], "syscalls": [] }

Após um ou dois dias de operação normal, você conhece as chamadas realmente necessárias e pode refinar a allowlist com precisão. Isso é muito mais seguro do que adotar uma lista pronta.

3. Deploy em Docker e Kubernetes

3.1 Docker

docker run -d --name php-app \
  --security-opt seccomp=/etc/docker/seccomp/php-fpm.json \
  --security-opt no-new-privileges:true \
  --read-only \
  --tmpfs /tmp:rw,noexec,nosuid,size=64m \
  -v /var/www/html:/var/www/html:ro \
  -v /var/lib/php/sessions:/var/lib/php/sessions:rw \
  php:8.3-fpm

noexec em /tmp é um complemento importante: mesmo que um atacante deposite um binário, não pode executá-lo diretamente. Combinado com bloqueio de execve, a probabilidade de sucesso cai drasticamente.

3.2 Kubernetes

apiVersion: v1
kind: Pod
metadata: { name: php-fpm }
spec:
  securityContext:
    seccompProfile: { type: Localhost, localhostProfile: profiles/php-fpm-seccomp.json }
  containers:
    - name: php
      image: php:8.3-fpm
      securityContext:
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        capabilities: { drop: ["ALL"] }
      volumeMounts:
        - { name: sessions, mountPath: /var/lib/php/sessions }
        - { name: tmp, mountPath: /tmp }
  volumes:
    - { name: sessions, emptyDir: {} }
    - { name: tmp, emptyDir: { medium: Memory } }

O caminho localhostProfile refere-se a /var/lib/kubelet/seccomp/ no node. Garanta que o perfil seja distribuído identicamente em todos os nodes.

4. eBPF: visibilidade em vez de bloqueio

Seccomp previne, eBPF explica. Com bpftrace ou Tetragon, tentativas de execução podem ser observadas mesmo quando já bloqueadas.

sudo bpftrace -e 'tracepoint:syscalls:sys_enter_execve / comm == "php-fpm" || comm == "php-fpm8.3" / { printf("%s pid=%d comm=%s file=%s\n", strftime("%H:%M:%S", nsecs), pid, comm, str(args->filename)); }'

A combinação é decisiva: Seccomp impede a execução, eBPF documenta a tentativa. Assim surge um trilha de auditoria confiável sem que o atacante precise ter sucesso.

5. Validação e rollback

sudo docker run --rm --security-opt seccomp=/etc/docker/seccomp/php-fpm.json php:8.3-fpm php -v
sudo docker exec php-app sh -c 'echo test' || echo "execve bloqueado (esperado)"
curl -sS https://example.com/health -o /dev/null -w '%{http_code}\n'
curl -sS -F "file=@/etc/hostname" https://example.com/upload/ -o /dev/null -w '%{http_code}\n'
sudo journalctl -k --since "10 min ago" | grep -i seccomp

Rollback: remover container e subir sem perfil. Um rollback deve ser parte do plano de mudança. Na noite após um patch de emergência, a tentação de ativar um perfil rígido sem teste é grande; um rollback falho pode prolongar a indisponibilidade.

6. Integração em cadeias de audit e alerting

Eventos Seccomp caem no log do kernel e são visíveis apenas localmente. Para operação robusta, devem fluir para o mesmo pipeline de Falco, auditd e Suricata. Um evento de bloqueio de execve sozinho é um indício técnico; combinado com um burst multipart imediatamente anterior e um reinício de worker, torna-se um score de confiança alto. Defina essa correlação explicitamente e teste-a com requisições inofensivas antes de usá-la para alerting.

Atenção para logs não transbordarem. Com perfis mal configurados, uma única requisição pode gerar centenas de chamadas bloqueadas. Rate limits e deduplicação no log shipper evitam que os sinais reais se percam. Em Kubernetes, recomenda-se um DaemonSet que gerencie perfis e logs centralmente, em vez de manutenção manual por namespace.

7. Limites e riscos

Seccomp não protege contra corrupção de memória, vazamento de informação ou denial of service dentro do conjunto de syscalls permitidos. Um atacante ainda pode ler dados, manipular sessões ou crashar o worker. Além disso, uma allowlist muito estreita causa falhas facilmente mal interpretadas em situação de incidente. Comece com bloqueio de execve, meça e amplie gradualmente.

Outro ponto é a manutenibilidade. Perfis Seccomp devem ser revisados em upgrades de PHP, novas extensões e mudanças de imagem base. Automatize o smoke test na CI e versione o perfil junto com a imagem. Um perfil desatualizado é um risco latente de indisponibilidade.

8. FAQ

P1: O bloqueio quebra processamento de imagem ou envio de e-mail? Sim, se essas funções iniciam processos externos. Mova essas tarefas para workers ou sidecars separados e mantenha o pool principal PHP-FPM livre de execve.

P2: Como testar com segurança em produção? Comece com SCMP_ACT_LOG, colete chamadas, derive a allowlist, então mude para SCMP_ACT_ERRNO. Canary pods primeiro, depois rollout gradual.

P3: Seccomp substitui o patch? Não. Previne execução, não corrupção. Patcheie para 8.5.12 / 8.4.27 / 8.3.36 / 8.2.35 e desative session.upload_progress enquanto não for possível.

P4: Como verificar no_new_privs?

grep NoNewPrivs /proc/$(pgrep -f 'php-fpm: pool' | head -1)/status

Valor 1 significa ativo.

9. Conclusão

Zero-trust PHP-FPM significa substituir a suposição "o worker pode tudo" por uma allowlist explícita. Seccomp-BPF, no_new_privs, filesystem root read-only e observação eBPF formam juntos uma barreira que atua mesmo após uma corrupção de memória bem-sucedida. Não substitui o patch, mas faz a diferença entre um incidente controlado e um comprometimento total.

10. Operação e monitoramento contínuo

Um perfil Seccomp não é "configurar e esquecer". Cada atualização de PHP, nova extensão ou mudança de imagem base pode introduzir chamadas de sistema não previstas. Estabeleça um processo de revisão: a cada mudança, execute o smoke test em staging com SCMP_ACT_LOG, compare a lista de chamadas com a versão anterior e documente divergências. Mantenha o perfil versionado no mesmo repositório da imagem e use a mesma tag. Assim, rollback de imagem reverte também o perfil, evitando incompatibilidades.

Para monitoramento, exporte eventos Seccomp para o SIEM e crie alertas para tentativas de execve bloqueadas, especialmente quando correlacionados com outros sinais (burst multipart, crash de worker). Revise mensalmente a taxa de bloqueios e investigue picos. Um aumento súbito pode indicar tentativa de ataque ou mudança de comportamento da aplicação; ambos exigem atenção. Documente incidentes e ajustes para manter histórico auditável e para que a equipe de plantão tenha contexto.

11. Considerações de performance

Seccomp adiciona overhead mínimo quando o perfil é simples e usa allowlist. O custo maior está na manutenção e no risco de bloquear chamadas necessárias. Perfis muito granulares podem degradar performance se exigirem muitas regras, mas na prática o impacto é desprezível. O ganho em segurança compensa. Meça antes e depois em staging com carga representativa para validar. Também considere o impacto de read-only root filesystem e PrivateTmp em tempo de inicialização: pequenas diferenças podem aparecer em cold starts, mas são aceitáveis para a maioria dos workloads.

12. Conclusão final

Hardening de syscalls é a última linha de defesa, não a primeira. Combine com patch, mitigação de engine e detecção em runtime para uma postura robusta. A chave é disciplina operacional: versionar, testar, monitorar e revisar. Sem isso, a medida se torna obsoleta e pode até causar indisponibilidade. Com isso, transforma-se em controle confiável que protege mesmo quando outras camadas falham.