Em resumo: Num site em produção, display_errors = Off pertence ao php.ini, juntamente com display_startup_errors = Off, log_errors = On e um registo de erros fora da raiz web. Além disso, os CMS e as frameworks têm de correr em modo de produção; caso contrário, as suas páginas de erro próprias mostram os pormenores. Bloqueados com php_admin_flag e verificados após cada atualização, os erros ficam no registo em vez de no navegador.
O que uma única mensagem de erro revela
Um aviso no topo da página, um «Fatal error» em vez do resumo da encomenda: estas saídas parecem inofensivas. Para um atacante, são um mapa. Mostram caminhos completos de ficheiros, nomes de bases de dados e tabelas, bibliotecas utilizadas e, muitas vezes, a versão do PHP.
Precisamente em situações como o PHP2Shell, em que se procuram servidores vulneráveis em massa, um servidor deve revelar o mínimo possível sobre si. Isso não fecha nenhuma vulnerabilidade no interpretador PHP, mas dificulta cada passo seguinte a uma intrusão e torna os ataques dirigidos à sua aplicação mais trabalhosos.
Acresce a proteção de dados. As mensagens de erro podem conter partes de pedidos, por exemplo endereços de e-mail de formulários. O que aparece no navegador de um visitante deixa de estar sob o seu controlo.
Os valores por defeito não foram pensados para a produção
Muitos partem do princípio de que o PHP oculta os erros por si. O manual do PHP diz outra coisa: o valor por defeito integrado do display_errors é 1, e o display_startup_errors está também ativado por defeito desde o PHP 8.0. Só o modelo fornecido php.ini-production define ambos como Off. O manual descreve expressamente o display_errors como uma funcionalidade de desenvolvimento que nunca deve ser usada em sistemas na Internet.
Se o seu servidor usa o modelo de produção não é evidente. As imagens de contentor arrancam por vezes sem qualquer php.ini, os painéis de alojamento definem valores próprios e, após uma mudança, copia-se facilmente o modelo de desenvolvimento.
display_errors off: as definições em resumo
Os valores seguintes fazem sentido para servidores de produção. As constantes como E_ALL só funcionam no php.ini; na configuração do Apache não têm, segundo o manual do PHP, qualquer significado e têm de ser indicadas como número.
| Definição | Produção | Finalidade |
|---|---|---|
display_errors |
Off |
nenhum erro no navegador |
display_startup_errors |
Off |
nenhum erro do arranque do PHP na saída |
log_errors |
On |
escrever os erros no registo |
error_log |
caminho fora da raiz web | ficheiro de registo próprio por site ou pool |
error_reporting |
E_ALL |
registar todos os erros, não os mostrar |
html_errors |
Off |
texto simples em vez de HTML nos registos |
zend.exception_ignore_args |
On |
sem argumentos de funções nos stack traces |
fatal_error_backtraces |
0 até à atualização (apenas PHP 8.5) |
medida transitória segundo o php.net |
O zend.exception_ignore_args é particularmente valioso. Sem esta definição, os stack traces em registos e páginas de erro podem conter os argumentos das chamadas de funções e, portanto, também palavras-passe ou tokens que estavam a ser processados.
; php.ini da SAPI web
display_errors = Off
display_startup_errors = Off
log_errors = On
error_log = /var/log/php/website-fehler.log
error_reporting = E_ALL
html_errors = Off
zend.exception_ignore_args = On
; apenas PHP 8.5, medida transitória até à atualização para 8.5.12:
; fatal_error_backtraces = 0
No pool FPM, bloqueie adicionalmente os valores mais importantes, para que nenhuma aplicação os volte a ativar com ini_set():
; p. ex. /etc/php/8.3/fpm/pool.d/seite1.conf
php_admin_flag[display_errors] = off
php_admin_flag[display_startup_errors] = off
php_admin_flag[log_errors] = on
php_admin_value[error_log] = /var/log/php/seite1-fehler.log
Depois, reinicie o respetivo serviço PHP. Como funciona exatamente o bloqueio é explicado no artigo php_admin_value: bloquear definições contra a sobrescrita. Encontra uma configuração de base completa em Configurar o php.ini com segurança.
O segundo interruptor: o modo de depuração da aplicação
O PHP é só metade do caminho. As frameworks modernas intercetam elas próprias os erros e mostram páginas de erro próprias, muitas vezes muito detalhadas. No modo de depuração, contêm excertos do código-fonte, valores de configuração e, por vezes, variáveis de ambiente. Estas páginas aparecem mesmo que o display_errors esteja desativado no PHP.
Verifique por isso, para cada aplicação, o interruptor próprio. No WordPress, são o WP_DEBUG e o WP_DEBUG_DISPLAY no wp-config.php; no Laravel e no Symfony, o APP_DEBUG e o ambiente no ficheiro .env; noutros sistemas, o modo de produção conforme a documentação do fabricante. Num sistema em produção, o modo de depuração tem de estar permanentemente desativado; mesmo «só por um instante para testar» é um risco.
Como se tratam corretamente os erros na aplicação, em vez de os suprimir, é descrito em Erros e exceções em PHP.
Como encontrar saídas de erro antes de outros o fazerem
Definir uma vez não basta. As saídas de erro regressam após atualizações, mudanças ou alterações no painel. Um breve procedimento de verificação ajuda:
- Verificar os valores efetivos no contexto web, não apenas na linha de comandos, por exemplo com um ficheiro de teste colocado por breves instantes e eliminado logo a seguir.
- Controlar o modo de depuração de cada aplicação no respetivo ficheiro de configuração.
- Pesquisar nas páginas entregues textos de erro típicos, como no bloco de código abaixo.
- No motor de busca, procurar com uma consulta
site:sobre o seu domínio termos como «Fatal error» ou «Warning». - Verificar se o registo de erros está a ser escrito e não se encontra na raiz web.
# Pesquisar nas páginas entregues textos de erro do PHP (apenas domínios próprios)
for url in https://www.example.de/ https://www.example.de/kontakt/; do
curl -s "$url" | grep -E -o "(Fatal error|Warning|Notice|Deprecated|Stack trace)[^<]{0,80}" | head -3
done
# O registo de erros está a ser escrito?
ls -l /var/log/php/
Esconder erros não substitui corrigi-los. Um registo cheio de avisos é um indício de necessidade de manutenção e, após uma intrusão, é muitas vezes um vestígio importante. O que deve ou não registar é descrito em Registar com segurança: registar erros sem fuga de dados.
Conclusão: silencioso no navegador, falador no registo
A regra para servidores de produção é simples: os erros pertencem ao registo, não ao site. Na prática, falha por causa de valores por defeito que não foram pensados para a produção, de interruptores de depuração em aplicações e de configurações que regridem após atualizações ou mudanças.
Quem desativa display_errors e display_startup_errors, bloqueia os valores no pool, controla o modo de depuração da aplicação e verifica o resultado regularmente fecha uma das fontes mais frequentes de divulgação desnecessária de informação. O esforço é reduzido e o efeito dura, desde que a verificação continue a fazer parte de cada atualização.
Um ponto não deve passar despercebido: o registo de erros não é um caixote do lixo. Quem o lê regularmente reconhece atualizações defeituosas, funções obsoletas e, por vezes, os primeiros vestígios de um ataque, muito antes de os clientes notarem alguma coisa.
Como ajuda concretamente a PHP SOS?
- Verificar: lemos as definições de erro efetivas por site e por pool e pesquisamos nas páginas entregues saídas de erro.
- Alterar: definimos as configurações do PHP e da aplicação para a operação em produção e bloqueamo-las contra sobrescrita.
- Configurar registos: os registos de erros ficam fora da raiz web, são rodados e estão acessíveis aos seus programadores.
- Manter de forma duradoura: no contrato de manutenção de segurança, voltamos a verificar as definições após cada atualização.
Perguntas frequentes
O display_errors não está já desativado no PHP?
Não automaticamente. Segundo o manual do PHP, o valor por defeito integrado do display_errors é 1. Só o modelo fornecido php.ini-production define Off. Se o seu servidor usa este modelo depende da distribuição, do painel ou da imagem de contentor e deve ser verificado.
O display_errors = Off basta se o WordPress ou o Laravel estiverem em modo de depuração?
Não. Muitos CMS e frameworks trazem páginas de erro próprias, que podem mostrar pormenores independentemente do PHP. Desative por isso também o modo de depuração da aplicação, por exemplo WP_DEBUG_DISPLAY no WordPress ou APP_DEBUG no Laravel e no Symfony.
Para onde vão os erros quando deixam de ser mostrados?
Com log_errors = On, o PHP escreve-os no ficheiro indicado por error_log ou no registo do respetivo modo de operação, por exemplo o registo de erros do Apache ou do PHP-FPM. O ficheiro deve estar fora da raiz web e ser rodado regularmente.
O que tem o fatal_error_backtraces a ver com o PHP2Shell?
A definição existe desde o PHP 8.5 e está ativa por defeito. O php.net indica fatal_error_backtraces=0, com reinício posterior, como uma das medidas transitórias para o PHP 8.5, enquanto a atualização para 8.5.12 não for feita. Não substitui a atualização.