PHP unter Linux sicher ausführen

Häufig haben Webserver Schwächen in der Implementierung von PHP in den Apache Server, weil der Administrator nur unzureichend PHP und Apache konfiguriert. Der folgende Artikel bezieht sich auf das Betreiben von PHP mit Apache Server unter Linux (bei meinen Servern auf Debian Lenny) und fasst meine Kenntnisse über die Sicherheit von PHP unter Apache zusammen. Inbesondere behandelt der Artikel das Zusammenspiel mit mod_fcgi, dessen Installation ich hier erklärt habe.

Einbetten einer php.ini Datei pro VirtualHost bei fcgid

In meinem Tutorial zu fcgid unter Debian Lenny habe ich bereits an entsprechenden Stellen angemerkt, das man vielleicht pro VirtualHost eine eigene php.ini (dies ist die Konfigurationsdatei von PHP) haben sollte. Ich lege für jedes größere PHP Projekt z.B. phpbb, Joomla!, Drupal (und/oder jeden Kunden), welches auf meinem Webserver läuft, einen eigenen VirtualHost (mit fcgid Wrapper) an. Admins, die kein fcgid benutzen, können nur die globale Konfigurationsdatei von PHP für alle Skriptaufrufe ändern.

Dies hat den Vorteil, dass sich so eventuelle Sicherheitslücken nicht auf den gesamten Server ausbreiten und ich außerdem für jedes PHP Softwareprodukt höchstmögliche Sicherheitsparameter in der php.ini Datei setzen kann. Um dies zu realisieren gehst Du wie folgt, abweichend von meiner fcgid Anleitung (oder nachträglich), so vor:

Kopiere zunächst die php.ini Datei in Dein VirtualHost Konfigurationsverzeichnis:

$ cp /etc/php5/cgi/php.ini /var/www/deinhost/

Setze die Rechte richtig und das Immutable Bit:

$ chown www-data:deinuser /var/www/deinhost/php.ini
$ chmod 660 /var/www/deinhost/php.ini
$ chattr -V +i /var/www/deinhost/php.ini

ACHTUNG: Um Veränderungen an der php.ini vorzunehmen (wie in fast allen hier beschriebenen Einstellungen), darf das Immutable Bit erst am Schluss gesetzt werden. Aber nicht vergesssen! Um die Konfiguration später wieder zu entsichern benutzt Du:

$chattr -V -i /var/www/deinhost/php.ini

Bearbeite nun Deinen fcgid Wrapper unter /var/www/deinhost/conf_deinhost wie folgt:

#!/bin/sh
export PHPRC="/var/www/deinhost/"
exec /usr/bin/php5-cgi

Das ausgeführte PHP CGI des VirtualHost wird nun unter den Maßgaben der neuen php.ini ausgeführt. Damit die Einstellungen wirksam werden, bitte den Apache Server reloaden:

$ /etc/init.d/apache2 restart

Bestimmte PHP Funktionen verbieten

Nur wenig PHP Software nutzt alle in PHP verfügbaren Funktionen. Einige der Funktionen in PHP sind außerdem unsicher und gefährlich:

  • show_source erlaubt Programmcode aus eine PHP Datei auszulesen und Highlights für den Code zu erstellen. Jedoch kann diese Funktion missbraucht werden, um gezielt vertrauliche Daten aus Dateien auszulesen z.B. Passwörter aus Konfigurationsdateien für MySQL Datenbanken.
  • system, shell_exec, passthru, exec, popen und proc_open erlauben das Ausführen von Programmen unter den Rechten des PHP Programms. Das fatale an diese Funktionen ist, dass sie nicht durch open_basedir (dazu später mehr) beschränkt sind. Das bedeutet, ein Angreifer würde alle Dateien erreichen und Programme ausführen können. die der Benutzer des PHP Skripts bei einer interaktiven Anmeldung erreicht.
  • phpinfo gibt viele Informationen über den benutzten Webserver und die PHP Installation zurück. Wenn man die Ausführung verhindert, erschwert man eventuellen Angreifern herauszufinden, welche Sicherheitslücken existieren könnten. Einige unter Euch werden sicherlich sagen, dass ist "security by obscurity", die dürfen phpinfo gerne zulassen. ;-) Es ist jedem selbst überlassen den entstandenen Sicherheitsgewinn zu bewerten.

PHP hat wegen der Gefährlichkeit einiger Funktionen die Möglichkeit geschaffen, einige Funktionen zu deaktivieren. Um die oben genannten Funktionen zu deaktivieren, benutze die folgende Option in der php.ini Datei:

disable_functions = show_source, system, shell_exec, passthru, exec, phpinfo, popen, proc_open

Eventuell kann es hilfreich sein auch ini_set und ini_restore zu sperren, um Änderungen der PHP Einstellungen zur Laufzeit zu vebieten.

Verzeichniszugriffe einschränken mit open_basedir

Zum Beispiel mit der Funktion fopen() in PHP kann man auf Dateien und Verzeichnisse zugreifen. Ich möchte aber als Webserverbetreiber nicht, dass ein Kunde A auf diesem Wege Daten von Kunden B auslesen kann oder noch schlimmer: Ein Angreifer mittels eines eingeschleusten PHP Skripts meine Webserverkonfiguration unter /etc anfängt auszulesen.

Aus diesem Grund sollte man unbedingt die Option open_basedir setzen. Mit Ihr kann man Verzeichnisse angeben, auf die PHP bzw. der aktuelle PHP CGI Benutzer zugreifen darf. Hier eine sinnvolle Konfiguration für meine fcgid Konfiguration:

open_basedir = /var/www/deinhost/htdocs:/var/www/deinhost/tmp

Die Ordner, auf die PHP Zugriff haben soll, stehen hinter dem Gleichheitszeichen durch Doppelpunkte getrennt. Unterordner sind in die Zugriffserlaubnis einbezogen (SymLinks übrigens nicht, es sei denn für sie wird explizit das Verzeichnis gesetzt).

ACHTUNG: open_basedir beschränkt nicht den Zugriff von z.B. mit exec ausgeführten Programmen. Nachfolgender Code funktioniert auch mit open_basedir auf ein Docroot-Verzeichnis gesetzt:

<?php
   $var = shell_exec('cat /etc/passwd');
   echo $var;

Hinweis: Die Ordner tmp und sessions habe ich in meinem Tutorial zu fcgid aus Gründen der Einfachheit für das Tutorial weggelassen. Warum sie dennoch sinnvoll sind, erläutere ich gleich.

Hinweis: Ab PHP 5.3 kann open_basedir auch zur Laufzeit von Skripten mit ini_set() gesetzt werden. Sofern in der php.ini (oder Apache Konfiguration) Vorgaben gemacht wurden, können diese weiter verengt werden. Wurde beispielsweise in php.ini /var/www zugelassen, so kann mit ini_set() diese Vorgabe auf /var/www/web123 verengt werden. Wurden in der php.ini keine Vorgaben gemacht, so kann mit ini_set open_basedir frei gesetzt werden.

Globale Variablen: register_globals

Muss ich überhaupt noch dazu was schreiben? Also, PHP Skripte die "register_globals = on" benötigen, gehören verboten. Zum Glück will PHP registe_globals in Version 6 gänzlich verschwinden lassen! Sie haben in der Vergangenheit zu großen Sicherheitsproblemen in PHP geführt.

Ist register_globals aktiviert, werden Daten die per GET oder POST vom Benutzer gesendet werden, in direkte Variablen geschrieben, so dass sich skriptsinterne sichere Variablen von unsicheren Eingaben der Benutzer nicht getrennt werden. Hat der Programmierer dann unvorsichtig programmiert, hat man direkt ein Sicherheitsloch. Deshalb umbedingt (wie eigentlich standardmäßig in PHP 5 in der php.ini dies setzen:

register_globals = Off

Ressourcen Limitierungen

PHP Skripte sind (normlerweise) nicht für den Dauerbetrieb gedacht und haben daher eine eingeschränkte Laufzeit und Ressourcen. Über die folgenden Parameter in php.ini lässt sich die Skriptausführung begrenzen:

  • max_execution_time: Ein Skript läuft maximal so lange wie hier in Sekunden angegeben. Wird das Limit überschritten, beendet PHP automatisch die Ausführung. Dies ist insbesondere interessant, wenn Skripte Fehler enthalten und sich nicht selbst beenden. Sinnvolle Werte sind meist 30 oder 60 Sekunden, je nach Anforderungen des PHP Programms.
  • max_input_time: Solange parst PHP maximal eine PHP Datei. Dies verhindert, dass das Parsing stundenlang fortgesetzt wird und den Server sperrt. Sinnvolle Werte sind 30 oder 60 Sekunden, je nach Anforderungen der benutzten PHP Software.
  • memory_limit: Hiermit kann man den maximal verbrauchten RAM in MB einschränken. Wenn ein Webmaster (oder ein Angreifer, Stichwort: DOS) riesige Skripte versucht auszuführen, wird dies gestoppt und der Server so vor dem Einknicken bewahrt.

Beispielkonfiguration:

max_execution_time = 30     ; Maximum execution time of each script, in seconds
max_input_time = 30 ; Maximum amount of time each script may spend parsing request data
memory_limit = 16M      ; Maximum amount of memory a script may consume

Sessions absichern

Die Sessiondaten sollten aus Sicherheitsgründen immer getrennt von den temporären Daten gehalten werden, so dass PHP Skripte selbst nicht direkt darauf zugreifen können. Die Standardkonfiguration legt Temporärdaten und Sessiondaten in /tmp, was schlecht ist, da dort auch andere Programme hin schreiben und alle Nutzer Zugriff haben. Passend zu meiner fcgid Umgebung, setze ich den Sessionordner pro VirtualHost auf einen eigenen Spezialordner:

session.save_path = /var/www/deinhost/sessions
session.gc_probability = 1
session.gc_divisor     = 100

Eigener Ordner für temporäre Daten

Jedem VirtualHost spendiere ich auch seinen eigenen temporären Ordner. Warum man Daten trennen sollte, habe ich ja bereits bei den Sessions erklärt.

upload_tmp_dir = /var/www/deinhost/tmp/

PHP Safe Mode

PHP Entwickler erhofften sich mit dem Einsatz des PHP Safe Mode etliche Sicherheitsprobleme zu lösen. Mit der neuen PHP 6 Version wird es den Safe Mode nicht mehr geben. Man hat festgestellt, dass der Sicherheitsgewinn minimal ist und viele Administratoren darüber hinweg täuscht, dass trotzdem noch Sicherheitsprobleme auftauchen können. Als Ergänzung eines ausgeprägten Sicherheitskonzepts, kann der Safe Mode Sinn machen, jedoch als Allheilmittel ist er nutzlos. Ich persönlich verzichte schon seit Jahren auf den Einsatz von Safe Mode.

Anmerkung: Dieser Artikel ist noch nicht ganz fertig. Folgende Informationen werde ich noch ergänzen: Fehlerausgaben und expose_php, Variablenreihenfolge, Sessions für Host anpassen, PHP in Apache sicher einbinden

Tags: