Em resumo: Para configurar o Nginx para PHP com segurança, passe ao PHP-FPM apenas os scripts que realmente existem e, nas aplicações com front controller, apenas o index.php. A isto juntam-se dotfiles bloqueados como .git e .env, um servidor predefinido para nomes de anfitrião desconhecidos, limites de tamanho adequados e security.limit_extensions = .php no pool. Isto não protege contra vulnerabilidades do próprio PHP, mas limita o que pode ser executado após um erro.
As linhas mais perigosas estão na location PHP
Desde o PHP2Shell, muitas equipas verificam as suas versões e pools PHP-FPM, como descreve a página Proteger o Nginx com PHP-FPM, e reiniciam, após a atualização, todos os workers. Uma segunda olhada vai para a própria configuração do Nginx. É aí que se decide que ficheiros são sequer executados como PHP.
Em muitas configurações há um bloco que passa ao PHP-FPM todo o pedido a .php, esteja o ficheiro onde estiver. Se um ficheiro PHP introduzido for parar a uma pasta de uploads, o servidor executa-o. De uma falha com ficheiros, surge assim uma porta dos fundos.
Este artigo concentra-se no lado do Nginx: que diretivas contam, como atua a ordem dos blocos location e como testar o resultado.
Como o Nginx decide que bloco atua
Quem quiser configurar o Nginx para PHP com segurança tem de conhecer a escolha dos blocos location. Segundo a documentação do Nginx, decorre assim:
- Uma correspondência exata com
location =termina a pesquisa de imediato. - Entre as locations de prefixo, é retida a mais longa que corresponda. Se tiver
^~, deixam de ser verificadas expressões regulares. - Caso contrário, as expressões regulares (
~,~*) são verificadas pela ordem do ficheiro de configuração; ganha a primeira correspondência. - Se nenhuma expressão regular corresponder, aplica-se a location de prefixo retida.
Daqui decorrem duas regras práticas. Um bloqueio do PHP em pastas de uploads tem de ser criado como prefixo ^~ ou como expressão regular colocada antes do bloco PHP geral. E uma location de prefixo normal como location /uploads/ não protege, porque a expressão regular para .php a sobrepõe.
Uma configuração de base segura
O exemplo seguinte mostra um bloco server para uma aplicação com front controller, como é habitual no Laravel, Symfony, Shopware 6 ou em muitos desenvolvimentos próprios. Adapte caminhos, socket e certificados.
server {
listen 443 ssl;
server_name www.example.de;
root /var/www/seite1/public;
index index.php;
ssl_certificate /etc/ssl/seite1/fullchain.pem;
ssl_certificate_key /etc/ssl/seite1/privkey.pem;
server_tokens off;
client_max_body_size 32m;
# Permitir Let's Encrypt e outros caminhos conhecidos
location ^~ /.well-known/ {
try_files $uri =404;
}
# Bloquear ficheiros e pastas ocultos (.git, .env, .htaccess)
location ~ /\. {
deny all;
}
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
# Só o front controller é passado ao PHP-FPM
location = /index.php {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
fastcgi_hide_header X-Powered-By;
fastcgi_pass unix:/run/php/seite1.sock;
}
# Nenhum outro ficheiro PHP é executado
location ~ \.php$ {
return 404;
}
}
O $realpath_root resolve as ligações simbólicas. Isso é importante em implementações com uma ligação simbólica variável para a versão atual, para que o PHP-FPM e a OPcache vejam o caminho certo. Tenha também em conta: segundo a documentação do Nginx, as diretivas fastcgi_param só são herdadas de níveis superiores se não houver outras no nível atual. Por isso, o include fastcgi_params; pertence ao mesmo bloco.
Os CMS clássicos como WordPress, TYPO3 ou Contao têm vários pontos de entrada. Aí, substitui-se o bloco do front controller por uma location PHP geral que só passa ficheiros existentes (try_files $uri =404;) e bloqueia-se a execução em todas as pastas com permissão de escrita. Como se consegue isso está em Proibir a execução de PHP em pastas de uploads.
A segunda proteção no PHP-FPM
O Nginx decide o que é passado, o PHP-FPM decide o que executa. Segundo o manual do PHP, o PHP-FPM executa por defeito ficheiros com .php e .phar e recomenda ele próprio restringir a .php. Isso apanha erros de configuração no servidor web.
; no pool, p. ex. /etc/php/8.3/fpm/pool.d/seite1.conf
security.limit_extensions = .php
listen = /run/php/seite1.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
O socket só deve ser acessível ao Nginx. As portas FPM na rede, por exemplo 9000, não devem ser abertas para o exterior. Como o servidor, no seu conjunto, mostra apenas as portas necessárias é descrito em Firewall para servidores web: abrir só as portas necessárias.
Nomes de anfitrião desconhecidos e outros parâmetros
Sem um servidor predefinido próprio, o Nginx responde a pedidos para nomes de anfitrião desconhecidos com o primeiro bloco server que corresponda. As análises automatizadas que apenas se dirigem ao endereço IP vão então parar diretamente à sua aplicação. Um servidor predefinido próprio termina esses pedidos sem resposta.
server {
listen 80 default_server;
listen 443 ssl default_server;
server_name _;
ssl_reject_handshake on;
return 444;
}
Outros pontos que se têm revelado eficazes:
| Parâmetro | Recomendação | Finalidade |
|---|---|---|
server_tokens |
off |
nenhum número de versão em cabeçalhos e páginas de erro |
client_max_body_size |
adequado ao post_max_size |
rejeitar cedo pedidos grandes |
autoindex |
desligado (por defeito) | nenhuma listagem de diretórios |
fastcgi_hide_header |
X-Powered-By |
nenhuma versão do PHP para o exterior |
| Páginas de estado | pm.status_path apenas interno |
nenhum dado de operação público |
| Ficheiros de cópia de segurança | bloquear ou remover .sql, .zip, .bak na raiz web |
sem transferência de bases de dados |
Mais sobre indicações de versão está no artigo expose_php e cabeçalhos Server.
Outro parâmetro diz respeito aos métodos HTTP permitidos. A maioria dos sites precisa apenas de GET, HEAD e POST. Com limit_except, podem rejeitar-se outros métodos numa location; segundo a documentação do Nginx, permitir GET permite automaticamente também HEAD. Verifique antes se as interfaces da sua aplicação usam, por exemplo, PUT ou DELETE, pois as APIs REST fazem-no com frequência.
Testar antes de recarregar
Verifique cada alteração primeiro com nginx -t e só depois recarregue o Nginx. Em seguida, teste o comportamento a partir de fora com pedidos normais ao seu próprio domínio:
sudo nginx -t && sudo systemctl reload nginx
# Esperado: 403 ou 404
curl -s -o /dev/null -w "%{http_code}\n" https://www.example.de/.git/HEAD
curl -s -o /dev/null -w "%{http_code}\n" https://www.example.de/.env
# Esperado: 404, porque só o front controller é executado
curl -s -o /dev/null -w "%{http_code}\n" https://www.example.de/info.php
# Que blocos passam ao PHP-FPM?
sudo nginx -T 2>/dev/null | grep -nE "location|fastcgi_pass"
Antes de qualquer alteração maior, guarde com data a saída completa de nginx -T. Contém toda a configuração efetiva, incluindo todos os ficheiros incluídos. Se algo correr mal após uma alteração, a comparação mostra de imediato o que mudou. Na próxima vulnerabilidade crítica, tem assim também uma base fiável para o levantamento, sem ter de reunir ficheiros primeiro.
Altere a configuração primeiro num ambiente de teste ou numa cópia do bloco server. Um ponto de entrada esquecido, por exemplo um callback de pagamento, só se nota, caso contrário, quando as encomendas deixam de chegar.
Como ajuda concretamente a PHP SOS?
- Ler a configuração: analisamos toda a configuração do Nginx com
nginx -Te associamos cada passagem ao PHP ao seu site e ao seu pool. - Definir os pontos de entrada: em conjunto com os seus programadores, determinamos que scripts têm de ser executados e bloqueamos o resto.
- Aplicar e testar: adaptamos blocos server e pools, verificamos com
nginx -te testamos formulários, meios de pagamento e uploads. - Proteger de forma duradoura: a configuração passa a fazer parte do nosso reforço de segurança de servidores e da documentação do seu servidor.
Perguntas frequentes
O Nginx tem de ser reiniciado após uma atualização do PHP?
Para o patch do PHP, não, porque o Nginx não contém PHP. Têm de ser reiniciados todos os serviços PHP-FPM. Se alterar também a configuração do Nginx, verifique-a antes com nginx -t e recarregue depois o Nginx.
Por que passar apenas o index.php ao PHP?
Muitas aplicações modernas têm um único ponto de entrada, o front controller. Se apenas este ficheiro for passado ao PHP-FPM, um ficheiro PHP introduzido noutro local não pode ser executado através do navegador. Em sistemas com muitos pontos de entrada, este padrão não é possível.
O que traz o security.limit_extensions no PHP-FPM?
A definição determina que extensões de ficheiro o PHP-FPM executa como script. Segundo o manual do PHP, o valor por defeito é .php .phar; recomenda-se restringir a .php. Isto apanha erros na configuração do servidor web, mas não a substitui.
As regras do Nginx protegem contra o PHP2Shell?
Não. O PHP2Shell está no interpretador PHP e só se fecha com a atualização para uma versão corrigida e o reinício de todos os workers. Uma configuração segura do Nginx reduz a superfície de ataque e limita os danos após uma intrusão.