PHP-Anwendungen unter eigenem Systembenutzer ausführen


Kaum einer meiner Server läuft nur mit einer einzigen Anwendung. Der Webserver, auf dem dieser Blog läuft, beherbergt nicht nur thomas-leister.de, sondern auch noch zwei andere Blogs und weitere PHP-basierte Webdienste, die ich zur Verfügung stelle. Aus Sicht eines Hackers ein lohnenswertes Ziel: Wird über eine PHP-Anwendung wie z.B. einen schlecht gepflegten Wordpress-Blog Zugriff auf das Dateisystem des Servers erlangt, kann der Hacker nicht nur die unsichere Webapplikation angreifen, sondern auch alle anderen Anwendungen, die unter dem PHP-User laufen. Aus diesem Grund ist es sinnvoll, die PHP-Anwendungen etwas voneinander abzuschotten, sodass ein Angreifer von der einen PHP-Anwendung aus keine andere PHP-Anwendung beeinträchtigen kann. Dies erreicht man, indem man für jede Anwendung einen eigenen User anlegt, der PHP ausführt. Die Verzeichnisrechte werden so gesetzt, dass nur der jeweils zugedachte PHP-User Zugriff auf die Anwendungsdateien hat und keine weiteren Änderungen im Dateisystem vornehmen kann.

Die folgende Methode zur Aufteilung in verschiedene PHP-Benutzer ist nur für Webserver geeignet, die zur PHP-Verarbeitung einen internen PHP-Socket nutzen (PHP-FPM, z.B. mit Nginx). Im Folgenden werde ich beschreiben, wie sich eine Webanwendung unter einem extra dafür angelegten User ausführen lässt.

Schritt 1: Einen neuen PHP-User anlegen

Zuerst wird ein neues Linux-Systembenutzer erstellt, unter dem später die jeweiligen PHP-Prozesse laufen werden, die die Webanwendung ausführen. Wichtig ist, dass der Benutzer sich nicht selbst einloggen kann - vor allem nicht via SSH! Ich nenne den Benutzer php-meineanwendung:

useradd --shell /bin/false php-meineanwendung

Mit “–shell /bin/false” wird der Zugriff auf die Shell verhindert. So kann sich der Benutzer weder einloggen, noch Shell-Kommandos ausführen. Manchmal ist es jedoch notwendig, dem PHP-User zu ermöglichen, Shell-Kommandos auszuführen, z.B. wenn via php-cli Cronjobs erledigt werden, die ein bestimmtes PHP Skript aufrufen. (z.B. bei OwnCloud). In diesem Fall erstellt man den neuen Benutzer so:

useradd php-meineanwendung

… und sperrt danach den Login:

usermod -L php-meineanwendung

Der neue User ist angelegt. Weiter geht es mit dem neuen PHP Worker-Pool

Schritt 2: Einen neuen PHP-Workerpool anlegen

Für die PHP-Anwendung wird ein neuer PHP-Workerpool angelegt, dessen PHP-Workerprozesse unter dem eben angelegten User laufen. Unter Ubuntu Server navigiert ihr dazu in das Verzeichnis /etc/php5/fpm/pool/ und legt eine neue Datei “meineanwendung.conf” an:

;
; meineanwendung pool
;

[meineanwendung]

user = php-meineanwendung
group = php-meineanwendung

listen = /var/run/php-meineanwendung.sock


; Set permissions for unix socket, if one is used. In Linux, read/write
; permissions must be set in order to allow connections from a web server. Many
; BSD-derived systems allow connections regardless of permissions.
; Default Values: user and group are set as the running user
; mode is set to 0660
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

pm = dynamic

pm.max_children = 10
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3

env[HOSTNAME] = $HOSTNAME
env[PATH] = /usr/local/bin:/usr/bin:/bin
env[TMP] = /tmp
env[TMPDIR] = /tmp
env[TEMP] = /tmp

php_admin_value[open_basedir] = /tmp:/var/www/meineanwendung

“meineanwendung” ersetzt ihr durch den Namen eurer Anwendung, z.B. “owncloud”. In der obigen Konfiguration werden Benutzername und Gruppe, unter der der Pool laufen soll, auf den php-meineanwendung User gesetzt. Außerdem wird der Socket-Pfand festgelegt, unter dem der Pool später für den Webserver erreichbar sein soll (“listen”). Mit “listen.owner”, “listen.group”, und “listen.mode” wird erreicht, dass nur der Webserver-User namens “www-data” (Standard für Nginx / Apache unter Ubuntu Server) auf den PHP-Socket zugreifen darf. Die übrigen Parameter stellen das Verhalten des Pools bei größer werdender Belastung ein und setzen Umgebungsvariablen für PHP. Weitere Infos dazu findet ihr in der Konfiguration eures Standard-Pools in der der Datei “www.conf” (Ubuntu Server). In der letzten Zeile werden die Verzeichnisse angegeben, auf die die PHP-Prozesse Zugriff haben sollen, also /tmp und /var/www/meineanwendung als Beispiel für das Verzeichnis, in welches die PHP-Anwendung installiert wurde. Die Angaben lassen sich beliebig mit weiteren Verzeichnispfaden erweitern. Achtet aber auf den trennenden Doppelpunkt zwischen den Pfaden!

Der neue Pool ist fertig konfiguriert. Startet euren PHP-FPM Prozess neu, damit die Änderungen übernommen werden. (Unter Ubuntu Server: service php5-fpm restart)

Schritt 3: Der PHP-Webanwendung einen eigenen Socket / Pool zuteilen

Weiter geht es mit der Konfiguration des Webservers. Ich beziehe mich im Folgenden auf den Nginx-Webserver. Die Änderungen sind für andere Webserver ähnlich. Wichtig ist eigentlich nur, dass nicht mehr der alte PHP-Socket (oftmals unter /var/run/php5-fpm.sock) genutzt wird, sondern der neue unter /var/run/php-meineanwendung.sock.

Unter Nginx nutze ich eine extra Upstream-Definition für den neuen PHP-Socket: (in /etc/nginx/nginx.conf im “http”-Bereich):

upstream php-meineanwendung {
    server unix:/var/run/php-meineanwendung.sock;
}

Im location-Block der PHP-Anwendung muss dem fastcgi_pass -Attribut der Name des Upstreams übergeben werden:

fastcgi_pass php-meineanwendung;

Die Webserver-Konfiguration muss neu geladen werden. (Unter Ubuntu: service nginx reload)

Schritt 4: Verzeichnisrechte anpassen

Der PHP-User “php-meineanwendung” bekommt nun Besitzrechte über das Verzeichnis, in dem die Webanwendung liegt:

chown -R php-meineanwendung /var/www/meineanwendung

Der Webserver (User www-data) benötigt nur lesenden Zugriff auf statische Dateien (JavaScript, CSS, Grafiken, …)

chgrp -R www-data /var/www/meineanwendung

Alle Rechte für den PHP-User, Leserechte für den Webserver und keine Rechte für alle anderen:

chmod -R 750 /var/www/meineanwendung

Fertig!

Die Verzeichnisrechte lassen sich noch verfeinern. So könnte man z.B. dem PHP-User nur schreibenden Zugriff auf jene Verzeichnisse geben, in die Dateien hochgeladen werden. Auch den Webserver könnte man nur auf bestimmte Verzeichnisse zugreifen lassen (also z.B. nur auf Verzeichnisse, die statische Dateien enthalten). Wie man die Verzeichnisrechte im einzelnen setzt, hängt jedoch sehr von der Anwendung ab und kann nicht pauschal gesagt werden.

Die erste Anwendung wird nun unter einem eigenen User ausgeführt. Wiederholt man die Schritte für jede laufende Webanwendung, ist wieder einmal etwas für die Sicherheit getan.