PHP 5.3 sicher machen – Ein kleines Tutorial
Wer seinen Webserver und damit auch PHP selber installiert, hat den großen Vorteil alles selber bestimmen zu können, das Installations-Verzeichnis, die Extensions, Ort der php.ini und natürlich sämtliche Konfigurations-Einstellungen. So viel Freiheit bringt aber auch die Verantwortung mit sich die PHP-Installation für den beabsichtigten Einsatzzweck so gut wie möglich abzusichern. Aber auf was muss man achten, welche php.ini-Einstellungen sind ein [MUSS] und welche sollte man vermeiden?
Einen Webserver sicher zu machen ist ein weites Feld und man muss sich diesbezüglich um viele Bereiche kümmern. Ob das nun der Apache, der Mailserver, die MySQL-Datenbank, DNS- oder FTP-Server oder eben PHP ist. Bei der Installation und Einrichtung all dieser Serverbereiche sollte immer auch ein scharfes Auge in Richtung Server-Sicherheit geworfen werden. Eine allumfassende Anleitung zur sicheren Einrichtung eines kompletten Webservers für alle möglichen Einsatzzwecke würde den Rahmen eines Blog-Artikels bei weitem sprengen. Deshalb geht es heute speziell um die Konfigurations-Einstellungen von PHP 5.3 (php.ini) auf einem für eigene Projekte, also selbst genutzten Webserver.
Sprach-Optionen:
safe_mode = Off — Der Safemode ist seit PHP v5.3 als deprecated (also veraltet) gekennzeichnet. Man sollte diesen also, auch wenn “safe” ja irgendwie nach Sicherheit klingt, nicht mehr benutzen. Für einen selbst genutzten Server wäre dies auch nicht unbedingt empfehlenswert.
open_basedir = “/www/htdocs:/includepath:/php_tempdir” — Mit open_basedir kann man den Datei-Zugriff von PHP auf ein bestimmtes oder mehrere Verzeichnisse (incl. Unterverzeichnisse) im Verzeichnis-Baum des Servers begrenzen. In der Regel sollte man hier drei Verzeichnisse angeben: 1. das htdocs-Verzeichnis (also das Verzeichnis ab dem die Dokumente und Skripte des/der Projekte zu finden sind), 2. ein evtl. vorhandenes Include-Verzeichnis (also der Ordner in dem die, wenn vorhanden, von allen Scripten/Projekten gemeinsam benutzbaren PHP include-Dateien/Librarys/Frameworks usw. liegen) und 3. wenn auf dem Server befindliche Projekte z.B. mit File-Uploads arbeiten, gehört hier auch der Pfad zum PHP-”upload_tmp_dir” hinein (also zu dem Verzeichnis in dem PHP upload-Files ablegt). Sonst kann man nach dem Upload auf die hoch geladene Datei nicht zugreifen. Wer gut ist und mehrere Projekte auf dem Server hostet “verengt” dann noch die open_basedir-Einstellung innerhalb des für das Projekt zuständigen Apache-vHost auf das Verzeichnis des Projektes. Das könnte dann so aussehen:
php_admin_value open_basedir "/www/htdocs/projekt_xy:/includepath:/php_tempdir"
Hinweis für Windows-Server: Hier trennt man die einzelnen Pfade mittels Semikolon und nicht mit einem Doppelpunkt. Schließlich wird der Doppelpunkt für den Laufwerks-Buchstaben benötigt.
disable_functions = exec,passthru,proc_open,shell_exec,system,popen — Mit Hilfe dieser Option kann man eine oder mehrere PHP-Funktionen deaktivieren. Unter Sicherheits-Gesichtspunkten kommen dafür Funktionen in Frage welche zum “ausbrechen” aus der PHP-Umgebung bzw. zum Zugriff auf den restlichen Server missbraucht werden könnten. Mit der Funktion shell_exec z.B. kann man ein Kommando auf der Shell ausführen, das auf diesem Weg der ganze Server gekapert werden könnte kann sich jeder denken. Wer also derartige Funktionen nicht braucht kann sie getrost deaktivieren.
expose_php = Off — Mit dieser Einstellung wird verhindert das sich PHP im HTTP-Header zu erkennen gibt (X-Powered-By: PHP/5.3.x). Für einen potentiellen Angreifer wäre die Information ob PHP bzw. welche PHP-Version läuft schon mal nicht uninteressant. Wer alle URL’s konsequent z.B. mit mod_rewrite umschreibt, so das alle Webseiten nach Außen z.B. in der Form “*.html” erscheinen, kann sogar kaschieren das PHP überhaupt auf dem Server läuft. Weiter unten beschreibe ich diesbezüglich noch eine weitere Einstellung die aber primär nicht zu PHP gehört.
Fehler-Behandlung:
error_reporting = E_ERROR|E_WARNING|E_PARSE — Im Gegensatz zu einigen anderen im Internet zu findenden Tipps sollten die PHP-Fehlermeldungen meiner Meinung nach nicht gänzlich abgeschaltet werden. Eine denkbare Einstellung ist hier zu sehen. Die wichtigsten Fehler sollte PHP nach wie vor melden, sonst tappen wir später bei evtl. auftretenden Problemen mit einem Script im Dunkeln. Für alle die jetzt aufstehen und schreien: “Man muss doch verhindern das Fehlermeldungen im Browser zu sehen sind!”… Richtig! Setzen und weiter lesen!
display_errors = Off — [MUSS] Diese Einstellung nun verhindert das Fehlermeldungen ausgegeben und zum Browser gesendet werden. Fehlermeldungen sind für Angreifer eine schöne Informations-Quelle. Nicht nur weil sie auf Fehler im Script quasi direkt hinweisen, allein schon der angezeigte Pfad zum Script lässt tiefe Einblicke in die Server-Verzeichnisstruktur zu. Deshalb gehören Fehler auf Produktions-Systemen niemals angezeigt!
display_startup_errors = Off — [MUSS] Für Fehler die während der Startsequenz von PHP auftreten gilt natürlich das selbe, gehören also abgeschaltet.
log_errors = On — Damit wir aber nun bei Problemen nicht gänzlich im Dunkeln stehen müssen die oben eingestellten Fehlermeldungen nun irgendwie den Weg in Webmasters Auge finden… also soll PHP Fehler loggen.
error_log = “/pfad/zum/logdir/phperror.log” — Damit PHP die Fehler nicht “irgendwo” loggt kann man mit dieser Einstellung eine entsprechende Datei festlegen. Ab und zu mal einen Blick in dieses Logfile geworfen hilft uns dann, nicht nur bei Problemen den Fehler zu finden, sondern deckt auch evtl. vorhandene Fehler-Situationen auf, die wir sonst vielleicht nie gesehen hätten.
Daten-Behandlung:
register_globals = Off — [MUSS] Diese Einstellung verhindert, daß an ein Script übergebe Variablen (über einen URL-Parameter oder eine entsprechende HTTP-POST Anfrage) “einfach so” im PHP-Code erscheinen, welche vom Script-Programmierer vielleicht gar nicht vorgesehen sind. Das Einschleusen von “falschen Variablen” wird so verhindert. Statt dessen sind übergebene Variablen über die Superglobals “$_GET” und “$_POST” verfügbar. Diese Option auf Off zu setzen halte ich für dringend empfohlen. Eigentlich ist sie schon seit PHP 4.2 standardmäßig auf Off. Trotzdem wird “register_globals” auch heute noch manchmal auf On gesetzt, meist um ältere Scripte zu fahren. Seit PHP 5.3 ist diese Option als veraltet gekennzeichnet, dürfte also in späteren Versionen komplett entfallen.
register_long_arrays = Off — Diese Option stellt zwar nicht so sehr ein Sicherheits-Problem dar, sollte aber trotzdem deaktiviert werden, es sei denn ältere Scripte benötigen die “langen Variablen” noch. Wenn “register_long_arrays” auf On steht werden übergebene Get- oder Post- Parameter auch in “long arrays” wie z.B. “$HTTP_GET_VARS” oder “$HTTP_POST_VARS” registriert. Das ist zwar besser als “register_globals” zu benutzen aber nicht zukunftsfähig denn in PHP 6 wird diese Option nicht mehr vorhanden sein.
Pfad- und Verzeichnis-Einstellungen:
enable_dl = Off — Mit der Funktion dl(“…”); ist es möglich auf “nicht-multithreaded” -Webservern PHP-Erweiterungen zur Laufzeit nachzuladen. Dies könnte unter Umständen dazu benutzt werden etwaige “open_basedir“-Beschränkungen auszuhebeln. In den meisten Fällen wird diese Funktion aber nicht gebraucht da alle benötigten Extensions üblicher Weise schon in der php.ini geladen werden. Deshalb sollte man die Funktion dl(); mittels enable_dl = Off abschalten.
Fopen-Wrappers Einstellungen:
allow_url_include = Off – Normaler Weise kann man mittels include(), require(), include_once() und require_once() nicht nur lokale sondern auch externe Dateien, also Dateien die z.B. auf einem anderen Server liegen und mittels HTTP-Protokoll erreichbar sind, einbinden. Das sieht dann z.B. so aus:
include("http://irgendein.server.tld/php-zeugs/includefile.inc");
Und genau eine Code-Zeile dieser Art ist auch schon alles was ein Hacker in ein PHP-Script einschleusen muss um jeden x-beliebigen PHP-Code mittels include() nachzuladen. Wer also genau weiß das keins der eigenen Scripte tatsächlich PHP-Code mittels include() von anderen Servern holt bzw. braucht, und das dürfte bei den meisten so sein, der sollte die Option allow_url_include auf Off stellen.
Auf meinem Server…? Nein, da läuft kein PHP!
Wie im Punkt expose_php beschrieben kann man PHP so einstellen daß es sich nicht zu erkennen gibt. Dies funktioniert aber nur wenn man dieses “Geheimnis” nicht gleich wieder durch die Verwendung von Scripten mit der Endung “.php” preis gibt. Eine Möglichkeit PHP ganz zu verstecken ist, wie ich oben bereits erwähnt habe, mod_rewrite, mit dessen Hilfe man z.B. auch Datei-Endungen umschreiben kann. Eine Weitere Möglichkeit besteht darin den Webserver, also z.B. den Apache, anzuweisen auch Dateien mit anderen Endungen als “.php” mit PHP zu parsen. Eine entsprechende Einstellung in der httpd.conf des Apache könnte z.B. so aussehen:
AddType application/x-httpd-php .html .htm
Mit dieser Konfiguration lässt der Apache auch alle “*.html” und “*.htm” Dateien durch PHP parsen. Man könnte also alle PHP-Scripte auch mit der Endung “.html” versehen. Sogar in der PHP-Dokumentation ist dieses kleine “PHP-Versteck-Spiel” beschrieben. Man sollte allerdings bedenken daß 1. die entsprechenden Scripte auch dafür ausgelegt werden müssen z.B. bei Verweisen oder include-Anweisungen eine andere Datei-Endung zu verwenden, und daß 2. diese Art der Verschleierung ganz eindeutig in die Kategorie “Security By Obscurity” fällt. Es bringt also nicht wirklich mehr Sicherheit, macht es einem potentiellen Hacker aber schwerer sein Ziel zu erreichen.
Und nun ist PHP sicher?
Sicher ist PHP genau dann wenn man das Netzkabel aus dem Server zieht…
Aber wenn wir mit Sorgfalt die php.ini unseres Servers durchgehen und bei jeder Einstellung genau überlegen was sie bewirkt, ob man sie braucht und ob sie Sicherheits-relevant ist dann können wir unseren Server, zumindest was PHP betrifft, ein Stückchen sicherer machen. Ich hoffe das dieser Artikel diesbezüglich eine kleine Hilfe darstellt. Ein Blick in die offizielle Beschreibung aller php.ini-Direktiven kann ich dabei nur empfehlen, zumal es auch abseits von Sicherheits-Überlegungen sehr interessant ist zu sehen, welche ini-Einstellungen es alles gibt und was sie bewirken. Im PHP-Manual findet man gleich ein ganzes Kapitel welches die Sicherheit rund um die PHP-Installation behandelt. Mann muss sich aber auch darüber im klaren sein das ein Großteil der Sicherheitslücken in den verwendeten Scripten stecken. Eine “sichere php.ini” nutzt nichts wenn ein Script auf dem Server Tür und Tor für einen Hacker öffnet. Das regelmäßige Update verwendeter Scripte sowie sorgfältiges programmieren unserer PHP-Projekte ist also genau so wichtig.
Kein Ende ohne Fazit?
Richtig.
Ein Blick in die php.ini kann niemals schaden. Auch Webmaster die auf ihrem Server “noch nie unter die Motorhaube” geguckt haben, und ich weiß es gibt eine Menge davon, sollten dies gelegentlich tun. Man kann dabei nur lernen und macht sich im Idealfall etwas unabhängiger von Experten die vielleicht im Notfall gerade nicht verfügbar sind. Es ist schade das man durch gute Einstellungen vereitelte Angriffe nicht zählen kann… aber wie sagt der vermeintliche “Roboter-Gott” so schön…: “Wenn man alles richtig macht, fällt den meisten Leuten überhaupt nicht auf, daß man überhaupt etwas gemacht hat.” Und mit diesem Zitat, bei dem jetzt jeder überlegen darf woher es stammt, möchte ich diesen Artikel schließen.




Hey, gut gelungen. Da werd ich mich am WE wohl mal hinsetzen und meine ini studieren.
Kurze Randfrage, wie verhielt sich das nochmal mit den Serverweiten Einstellungen und den Lokalen Einstellungen ?
Um für ein Web lokal” die ini zu ändern muss ich diese nur in das httpdoc legen, mit diesen Einstellungen kann ich jedoch nicht die Serverweiten überschreiben ?
So richtig ?
Also… Ob Du mit separaten php.ini für jedes Projekt auf Deinem Server arbeiten kannst hängt davon ab ob Du PHP als CGI-Variante oder Apache-Modul einbindest.
Um’s mal vereinfacht zu sagen: Wenn PHP als CGI eingebunden ist läd der Server PHP bei jedem Aufruf, ist PHP als Apache-Modul eingebunden wird PHP beim Start des Apache geladen.
Da PHP ja nur einmal eine php.ini laden kann folgt daraus daß nur in der CGI-Version “Projekt-eigene” php.ini’s verwendet werden können. In diesem Fall kannst Du das so wie in Deiner Frage beschrieben machen, beachte dabei aber: “user.ini-Dateien” und “die Konfigurationsdatei”.
Ich selber benutze auf meinen Servern fast ausschließlich PHP als Apache-Modul. Hier wird beim Apache-Start auch die “einzige” php.ini geladen. In dieser Konfiguration kann man aber, und so mache ich das, den einzelnen Projekten andere ini-Einstellungen zuweisen, in dem man die von der haupt-php.ini abweichenden Einstellungen in das Apache Config-File (httpd.conf), bzw. wenn die Projekte (vHosts) eigene Config-Files besitzen (z.b. “vhost-xy.conf”) in diese, hinzufügt. Das sieht dann z.B. so aus:
<VirtualHost 123.123.123.123:80>
ServerName projekt-xy.tld
DocumentRoot /www/htdocs/projekt_xy
…
…
php_admin_value open_basedir “/www/htdocs/projekt_xy:/includepath:/php_tempdir”
php_value error_reporting 32767
php_flag display_errors On
…
…
</VirtualHost>
Auf diese Weise kann ich für jedes Projekt (vHost) die PHP-Einstellungen “feinjustieren” und brauche dabei nur die Einstellungen setzen die von der haupt-php.ini abweichen.
Wichtig wäre noch zu sagen das du im Apache Configfile keine PHP-Konstanten verwenden kannst. Statt z.B.:
php_value error_reporting E_ALL
musst Du also:
php_value error_reporting 32767
schreiben.
Man kann einige Einstellungen sogar auf die gleiche Weise wie im Apache Config-File in der .htaccess setzen. Wichtig ist dabei zu wissen das nicht jede Einstellung überall gesetzt werden kann. Beispielsweise kann “open_basedir” nicht in der .htaccess gesetzt werden. Was wo gesetzt werden kann findet sich in der Beschreibung der ini-Einstellungen.