Kort sagt: På et live-website hører display_errors = Off hjemme i php.ini, sammen med display_startup_errors = Off, log_errors = On og et fejllog uden for webroden. Desuden skal CMS og frameworks køre i produktionstilstand, ellers viser deres egne fejlsider detaljerne. Låst med php_admin_flag og kontrolleret efter hver opdatering bliver fejl i loggen i stedet for i browseren.
Hvad en eneste fejlmeddelelse afslører
En advarsel i øverste sidekant, en "Fatal error" i stedet for ordreoversigten: Sådanne udskrifter ser harmløse ud. For en angriber er de et landkort. De viser fuldstændige filstier, navne på databaser og tabeller, anvendte biblioteker og ofte PHP-versionen.
Netop i situationer som PHP2Shell, hvor der søges massivt efter sårbare servere, bør en server afsløre så lidt som muligt om sig selv. Det lukker ingen sårbarhed i PHP-fortolkeren, men besværliggør ethvert videre skridt efter et indbrud og gør målrettede angreb på jeres applikation besværlige.
Hertil kommer databeskyttelsen. Fejlmeddelelser kan indeholde dele af forespørgsler, f.eks. e-mailadresser fra formularer. Det, der dukker op i en besøgendes browser, er ikke længere under jeres kontrol.
Standardværdierne er ikke tænkt til produktion
Mange går ud fra, at PHP af sig selv skjuler fejl. PHP-manualen siger noget andet: Den indbyggede standardværdi for display_errors er 1, og display_startup_errors er siden PHP 8.0 ligeledes slået til som standard. Først den medfølgende skabelon php.ini-production sætter begge på Off. Manualen kalder udtrykkeligt display_errors en udviklingsfunktion, som aldrig bør bruges på systemer på internettet.
Om jeres server bruger produktionsskabelonen, er ikke en selvfølge. Container-images starter til tider helt uden php.ini, hostingpaneler sætter egne værdier, og efter en flytning kopieres gerne udviklingsskabelonen.
display_errors off: indstillingerne i overblik
De følgende værdier er fornuftige til produktionsservere. Konstanter som E_ALL fungerer kun i php.ini; i Apache-konfigurationen har de ifølge PHP-manualen ingen betydning og skal angives som tal.
| Indstilling | Produktion | Formål |
|---|---|---|
display_errors |
Off |
ingen fejl i browseren |
display_startup_errors |
Off |
ingen fejl ved PHP's start i outputtet |
log_errors |
On |
skriv fejl i protokollen |
error_log |
sti uden for webroden | egen logfil pr. website eller pool |
error_reporting |
E_ALL |
protokollér alle fejl, vis dem ikke |
html_errors |
Off |
ren tekst i stedet for HTML i protokoller |
zend.exception_ignore_args |
On |
ingen funktionsargumenter i stacktraces |
fatal_error_backtraces |
0 indtil opdateringen (kun PHP 8.5) |
overgangsforanstaltning ifølge php.net |
zend.exception_ignore_args er særligt værdifuld. Uden denne indstilling kan stacktraces i logs og på fejlsider indeholde argumenterne fra funktionskald, altså også adgangskoder eller tokens, som netop blev behandlet.
; php.ini for web-SAPI'en
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
; kun PHP 8.5, overgangsforanstaltning indtil opdateringen til 8.5.12:
; fatal_error_backtraces = 0
I FPM-poolen låser I desuden de vigtigste værdier, så ingen applikation slår dem til igen med ini_set():
; f.eks. /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
Genstart derefter den pågældende PHP-tjeneste. Hvordan låsningen præcis fungerer, forklarer indlægget php_admin_value: Lås indstillinger mod overskrivning. En fuldstændig grundkonfiguration finder I under Konfigurér php.ini sikkert.
Den anden kontakt: applikationens debug-tilstand
PHP er kun den halve sandhed. Moderne frameworks opfanger selv fejl og viser egne, ofte meget udførlige fejlsider. I debug-tilstand indeholder de kildetekstuddrag, konfigurationsværdier og af og til miljøvariabler. Disse sider vises også, når display_errors er slået fra i PHP.
Kontrollér derfor for hver applikation dens egen kontakt. I WordPress er det WP_DEBUG og WP_DEBUG_DISPLAY i wp-config.php, i Laravel og Symfony APP_DEBUG og miljøet i .env-filen, i andre systemer produktionstilstanden ifølge producentens dokumentation. På et live-system hører debug-tilstanden varigt hjemme slået fra, også "bare kort for at teste" er en risiko.
Hvordan fejl i applikationen håndteres ordentligt i stedet for at blive undertrykt, beskriver Fejl og exceptions i PHP.
Sådan finder I fejludskrifter, før andre gør det
At sætte det én gang er ikke nok. Fejludskrifter vender tilbage efter opdateringer, flytninger eller panelændringer. Et kort kontrolforløb hjælper:
- Kontrollér de virksomme værdier i webkonteksten, ikke kun på kommandolinjen, f.eks. med en kort udlagt kontrolfil, der straks slettes igen.
- Kontrollér hver applikations debug-tilstand i dens konfigurationsfil.
- Gennemsøg leverede sider efter typiske fejltekster, som i kodeblokken nedenfor.
- Søg i søgemaskinen med en
site:-forespørgsel på jeres domæne efter begreber som "Fatal error" eller "Warning". - Kontrollér, at der skrives i fejllogget, og at det ikke ligger i webroden.
# Gennemsøg leverede sider efter PHP-fejltekster (kun egne domæner)
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
# Skrives der i fejllogget?
ls -l /var/log/php/
At skjule fejl er ingen erstatning for at rette dem. Et log fuldt af advarsler er et tegn på vedligeholdelsesbehov, og efter et indbrud er det ofte et vigtigt spor. Hvad I protokollerer, og hvad ikke, beskriver Log sikkert: Protokollér fejl uden datalæk.
Konklusion: Stille i browseren, snaksaligt i loggen
Reglen for produktionsservere er enkel: Fejl hører hjemme i protokollen, ikke på websitet. I praksis strander den på standardværdier, der ikke er tænkt til produktion, på debug-kontakter i applikationer og på konfigurationer, der falder tilbage efter opdateringer eller flytninger.
Den, der slår display_errors og display_startup_errors fra, låser værdierne i poolen, kontrollerer applikationens debug-tilstand og regelmæssigt kontrollerer resultatet, lukker en af de hyppigste kilder til unødig informationsafsløring. Indsatsen er lille, virkningen holder, så længe kontrollen forbliver en del af hver opdatering.
Ét punkt bør ikke gå tabt: Fejllogget er ingen skraldespand. Den, der læser det regelmæssigt, genkender fejlbehæftede opdateringer, forældede funktioner og af og til også de første spor af et angreb, længe før kunder bemærker noget.
Hvordan hjælper PHP SOS konkret?
- Kontrollér: Vi udlæser de virksomme fejlindstillinger pr. website og pool og gennemsøger leverede sider efter fejludskrifter.
- Omstil: Vi sætter PHP- og applikationsindstillinger til produktionsdrift og låser dem mod overskrivning.
- Opsæt protokoller: Fejllogs ligger uden for webroden, roteres og er tilgængelige for jeres udviklere.
- Hold det varigt: I sikkerhedsvedligeholdelsesaftalen kontrollerer vi indstillingerne påny efter hver opdatering.
Ofte stillede spørgsmål
Er display_errors i PHP ikke alligevel slået fra?
Ikke automatisk. Ifølge PHP-manualen er den indbyggede standardværdi for display_errors 1. Først den medfølgende skabelon php.ini-production sætter Off. Om jeres server bruger denne skabelon, afhænger af distribution, panel eller container-image og bør kontrolleres.
Er display_errors = Off nok, hvis WordPress eller Laravel kører i debug-tilstand?
Nej. Mange CMS og frameworks medbringer egne fejlsider, der uafhængigt af PHP kan vise detaljer. Slå derfor også applikationens debug-tilstand fra, for eksempel WP_DEBUG_DISPLAY i WordPress eller APP_DEBUG i Laravel og Symfony.
Hvor går fejlene hen, når de ikke længere vises?
Med log_errors = On skriver PHP dem i den fil, som error_log angiver, eller i den pågældende driftsforms protokol, for eksempel fejllogget fra Apache eller PHP-FPM. Filen bør ligge uden for webroden og roteres regelmæssigt.
Hvad har fatal_error_backtraces med PHP2Shell at gøre?
Indstillingen findes fra PHP 8.5 og er som standard aktiv. php.net nævner fatal_error_backtraces=0 med efterfølgende genstart som en af overgangsforanstaltningerne for PHP 8.5, så længe opdateringen til 8.5.12 endnu mangler. Den erstatter ikke opdateringen.