HowTo: Sicherer Webserver mit PHP + FastCGI
Nachdem ich mir die halbe letzte Nacht um die Ohren geschlagen habe und auch direkt heute Morgen an dieses Thema gedacht habe, dachte ich so bei mir, dass ich meine Erkenntnisse und Gedanken eigentlich prima in einem Howto verpacken könnte.
Worum geht es eigentlich? Es geht um sichere Webserver. Dabei allerdings weniger um Gefahren von außen, sondern um die Gefahr, die von innen kommt. Zwar gibt es immer mehr Nutzer, die einen eigenen Server verwalten und somit genau kontrollieren können, welche Informationen nach außen dringen, aber noch immer macht das Massenhosting einen Großteil der Homepages aus.
Massenhosting bedeutet, dass ein physikalischer Server von mehreren Homepages gleichzeitig genutzt wird, die allesamt parallel auf dem Server existieren. Zwar ist es kein großes Sicherheitsproblem, wenn die Nutzer per FTP ihre Daten hochladen und über HTTP abrufen können, problematisch wird es aber dann, wenn die hochgeladenen Daten Programme jeglicher Art sind, die auf dem Server ausgeführt werden können.
Gerade mit der unglaublich steigenden Bekanntheit von PHP, stiegen auch die Sicherheitsprobleme. Und ich habe nun einmal einige Gedanken zusammengetragen, wie man einen möglichst sicheren Webserver mit PHP-Unterstützung realisieren kann, da ich bei meiner Suche im Internet bisher leider noch nichts vergleichbares finden konnte.
Das Problem
Sicherheit steht immer im Gegensatz zur Bequemlichkeit. Eine Alarmanlage im Haus bringt große Sicherheit, stört mich aber sehr, wenn ich in der Nacht etwas erledigen muss und sie erst deaktivieren muss. Bodyguards bringen mir auch große Sicherheit nur schirmen sie mich eventuell unnötig ab und ich habe kaum noch Privatspäre.
Daher muss ein Kompromiss gefunden werden, der zum einen eine gute Sicherheit bietet, zum anderen aber auch dem Kunden sowenig Schwierigkeiten macht wie nur möglich.
Das grundlegende Problem ist folgendes:
Die meisten Webserver, auf denen PHP als Scriptsprache angeboten wird, nutzen das PHP-Modul für den Apache. Dieses Modul wird zentral in den Apache-Webserver eingebunden und steht danach allen Homepages, respektive Virtual-Hosts, zur Verfügung. Der Vorteil: Die Ausführung der PHP-Programme ist sehr schnell und die Konfiguration ist sehr einfach. Außerdem sind die PHP-Programme in dieser Konfiguration sehr flexibel und können nahezu jegliche Arbeiten verrichten, die gefordert werden.
Genau hier liegt das Problem. PHP-Programme, die über das Apache-Modul ausgeführt werden, werden im Linux/Unix-System unter dem Benutzer ausgeführt, unter dem auch der Apache ausgeführt wird. Natürlich muss der Apache auf sämtliche Dateien zugreifen können, die er an die Besucher der Internetseiten ausliefern möchte. Das bedeutet, sämtliche Dateien, die ausgeliedert werden sollen oder als PHP-Programm ausgeführt werden sollen, müssen für den Apache lesbar sein.
Im Umkehrschluss bedeutet das dann, ein PHP-Script, egal an welcher Stelle im System, welches über das Internet aufgerufen wird, hat Zugriff auf sämtliche Dateien, die vom Apache gelesen oder vom PHP-Modul ausgeführt werden sollen. Das umschließt die PHP-Programme selbst im Quelltext (jemand anderes kann also das geistige Eigentum stehlen), .inc-Dateien, die zb. Passwörter für den SQL-Zugriff enthalten oder .htpasswd-Dateien, die von Apache zum Schutz von Verzeichnissen genutzt werden.
Und genau diese Sicherheitslücke werden wir uns nun vornehmen.
Der Weg
Es gibt einige Möglichkeiten, das Problem zu lösen:
- PHP wird nicht mehr angeboten: Zugegeben, ein drastischer Schritt, den kein Anbieter von Webhosting gehen wird, aber sicher der wirksamste
- Safe-Mode: Safe-Mode sind verschiedene Funktionen innerhalb von PHP, die diese Sicherheitsprobleme gezielt angehen sollen. Zwei Funktionen werde ich in meiner Lösung einsetzen. Generell ist der Safe-Mode allerdings eher unwirksam, da er mehr Probleme erzeugt, denn löst. So streuben sich einige sehr bekannte PHP-Applikationen unter Safe-Mode zu laufen, da zb. populäre Funktionen, wie der Upload von Dateien immer wieder zu Problemen führen.
- abgespeckter Safe-Mode + disable_functions: Dies dürfte die beste Lösung für Administratoren sein, die einigermaßen “humane” Kunden haben. Der Safe-Mode wird generell ausgeschaltet. Mit der open_basedir-Richtlinie setzt man die PHP-Funktionen für den Dateizugriff auf das eigene Verzeichnis fest, so dass hier nur noch Dateien der eigenen Homepage angezeigt werden können. Schließlich werden mit disable_functions alle PHP-Funktionen ausgeschaltet, die normale Unix-Befehle ausführen können und somit dennoch fremde Dateien ausspionieren können.
Leider gibt es hier allerdings auch den Haken, dass manche Applikationen diese Funktionen zwingend benötigen, da sie auf externe Software angewiesen sind. Ein Beispiel ist Gallery. Hinzu kommt, dass disable_functions leider nur sehr unflexibel ist, da es nur global definiert werden kann.
- PHP-CGI + suexec: Fast meine Lösung. PHP wird als externe Software über einen sog. suexec-wrapper ausgeführt. Die genaue Bedeutung kommt später. Allerdings bietet diese Lösung eine generell schlechtere Performance als die Nutzung von PHP als Modul.
Die Lösung
Zunächst, warum PHP als CGI ausführen?
Nun, zusammen mit suexec wird PHP als der Benutzer ausgeführt, dem der virtuelle Webserver gehört. Das bedeutet natürlich, dass ich nur auf Dateien Zugriff habe, die ich mit meinen eigenen Berichtungen lesen darf. Die PHP-Scripte meines “Server-Nachbarn” werden mit seinen Berechtigungen ausgeführt. Sind die Berechtigungen sorgfältig gesetzt, kann diese Lösung als sicher erachtet werden.
Daher ist zu beachten, dass gerade auf die Berechtigungen ein großes Augenmerk gelegt werden. Diese ganze Lösung ist nämlich dann für die Katz, wenn Dateien trotzdem von jedem gelesen werden können.
Um die Abarbeitung der PHP-Scripte auch schnell zu machen, kommt noch eine Komponente hinzu: FastCGI FastCGI ist ein Apache-Modul mit sehr guten Caching-Möglichkeiten, bei dem PHP als Server gestartet wird und so die Aufträge schnell abgearbeitet werden können. Es gibt sogar die Möglichkeit, externe PHP-Server zu nutzen und so zb. ein Loadbalancing der PHP-Last zu erreichen.
Dieses Howto richtet sich weniger an die grundlegende Installation von Apache, FastCGI und PHP, da sie sich unter Gentoo, welches ich nutze, recht einfach gestaltet. Wer von Source installiert, findet dazu einige Howtos im Netz, die dann allerdings meiner Meinung nach zuwenig auf die Berechtigungen der Nutzer eingehen. Wichtig ist nur, dass beim Apache die suexec-Möglichkeiten eingebunden werden und PHP mit FastCGI-Unterstützung kompiliert wird.
Unter Gentoo reicht dazu ein:
emerge -v apache php-cgi mod_fastcgi
Natürlich sollte man sich noch seine entsprechenden USE-Variablen zusammensuchen, gerade PHP bietet da eine sehr umfangreiche Liste:
emerge -pv php-cgi
Wichtig ist natürlich mitlerweile auch USE=”apache2″, da wir für den Apache Version 2 kompilieren werden.
Nach der Installation der drei Komponenten müssen wir in der /etc/conf.d/apache2 das FastCGI-Modul aktivieren:
APACHE2_OPTS="-D SSL -D DAV -D FASTCGI"
Hier ist natürlich nur der Part “-D FASTCGI” der notwendige.
FastCGI unterscheidet zwischen drei Arten von Applikationsservern, also zb. drei Arten von PHP-Servern, die in Zukunft die Arbeit machen sollen.
- statische Server werden beim Start des Webservers gestartet und stehen dann dauerhaft zur Verfügung.
- dynamische Server werden dann gestartet, wenn eine Anfrage an den Server gestellt wird, die die Ausführung eines PHP-Scripts beinhaltet.
- externe Server können per TCP/UDP angesprochen werden und so zb. freie Resourcen anderer Server mit nutzen.
Hier muss man nun entscheiden, ob man virtuelle Server hat, die mit einer hohen Besucherfrequenz einen nutzen aus statischen Servern ziehen würden oder ob man virtuelle Server hat, auf denen nur sehr wenige Besucher sind. Gerade im Massenhosting macht es eher Sinn auf dynamische Server zu setzen, da das Gästebuch des lokalen Taubenzüchtervereins vielleicht einmal pro Monat aufgerufen wird. Natürlich bringt ein dynamischer Server eine leichte Verzögerung bei der ersten Anfrage. Ist der Server geladen steht er auch für weitere Anfragen des gleichen Benutzers zur Verfügung, bis er nach einer konfigurierbaren Zeit stirbt.
Da der dynamische Server zunächst Grundlage auch für die statischen Server ist kümmern wir uns erstmal darum. Folgende Zeilen kommen in den VirtualHost der jeweiligen Webservers:
ScriptAlias /cgi-bin /var/www/private/blog.commy.de/cgi-bin
AddHandler php-fastcgi .php
SetHandler fastcgi-script
Action php-fastcgi /cgi-bin/php
DirectoryIndex index.html index.shtml index.cgi index.php
AddType application/x-httpd-php .php
SuexecUserGroup "matthias" "users"
Sämtliche Anfragen nach .php-Dateien werden auf ein Shellscript unter dem relativen Pfad /cgi-bin/php geleitet. Aus diesem Grunde benötigt man natürlich die Definition eines cgi-bin-Verzeichnisses. Ich nutze dafür die ScriptAlias-Angabe, euch stehen natürlich auch die anderen Möglichkeiten zur Verfügung.
Damit der Server auch unter den korrekten Benutzerdaten läuft, benötigt man die SuexecUserGroup-Angabe. Unter Gentoo funktioniert es nicht, das PHP unter dem Apache-Benutzer apache zu starten. Daher sollte man hier ohnehin den jeweiligen Benutzer des Webservers angeben und eine allgemeine Gruppe (alternativ kann man natürlich auch eine Gruppe webuser o.ä. anlegen).
Um PHP nun zu aktivieren, benötigt man nun auch das Shell-Script im cgi-bin-Verzeichnis. Dazu nutzt man folgendes:
#!/bin/sh
umask 027
PHPRC="/etc/php/cgi-php4/"
export PHPRC
PHP_FCGI_CHILDREN=4
export PHP_FCGI_CHILDREN
exec /usr/bin/php-cgi
Die umask-Angabe werde ich später noch erklären.
PHPRC definiert den Ort, an dem der PHP-Server die PHP-Konfigurationsdateien finden kann. Hiermit ist es also sogar möglich, jedem Kunden zu ermöglichen, mit einer php.ini im entsprechenden Verzeichnis, eigene Parameter des PHP-Servers zu definieren.
PHP_FCGI_CHILDREN definiert die Anzahl von PHP-Instanzen, die initial gestartet werden sollen. Je nach Besucherfrequenz des Servers können dies mehr oder auch weniger sein.
exec startet schließlich PHP. Der Pfad sollte natürlich entsprechend angepasst werden. Hier zeigt sich aber ein weiterer Vorteil von fastcgi; Ohne andere Webserver auf dem Server zu stören, kann hierdurch eine neue PHP-Version getestet oder unterschiedliche PHP-Versionen auf einem physikalischen Server laufen.
In der /etc/apache2/httpd.conf fehlt nun noch die Angabe des zu nutzenden suexec-Wrappers:
FastCgiWrapper /usr/sbin/suexec2
Möchte man nun statische Server nutzen, richtet man diese unter Angabe des Pfades für das obige Shell-Script und der identischen Benutzer/Gruppen-Angaben mit folgenden Zeilen ein:
FastCgiServer /var/www/private/blog.commy.de/cgi-bin/php -user matthias -group users
FastCgiServer /var/www/_SERVICES/cgi-bin/php -user wwwsystem -group users
Mit diesen Zeilen würden dann beim Start von Apache zwei PHP-Server mit jeweils unterschiedlichen Benutzerdaten starten.
Nun wird es spannend. PHP läuft jetzt zwar soweit, aber in den Standardeinstellungen ist es wenig benutzer, weil wir immer noch Sicherheitsprobleme haben. Die können wir jetzt lösen, indem wir einige Dateiberechtigungen ändern.
Durch das suexec haben wir nun erreicht, dass PHP-Dateien und Dateien, die nur von PHP gelesen werden sollen, lediglich vom Benutzer gelesen werden brauchen. Die Gruppe oder alle anderen brauchen die Dateien nicht mehr lesen, was für PHP-Dateien schon einmal ein sehr großer Vorteil ist.
Um nun die größtmögliche Sicherheit zu erhalten, habe ich folgende Berechtigungen erarbeitet:
Es sollten alle Dateien und Verzeichnisse in dem Webserver-Verzeichnis eines Nutzers dem Nutzer gehören (klar). Die Gruppe sollte dem Webserver-Benutzer gehören (im Falle von Gentoo apache). Der Benutzer sollte alles können, die Gruppe sollte nur lesen und alle anderen sollten nichts können.
Das größte Problem sind hierbei Dateien, die durch PHP neu angelegt werden. Standardmäßig bekommen diese Dateien nämlich keine Rechte für Gruppen und alle anderen. Für geheime Daten ist das OK, für Daten die hochgeladen werden und wie bei der Gallery dann zb. resized werden, ist das aber sehr schlecht, da die dann nicht per Webbrowser angezeigt werden können.
Außerdem erhalten diese Dateien die Gruppenzugehörigkeit “users”, da wir unter “users” ja den PHP-Server laufen haben.
Letzteres können wir verhindern, in dem wir mit
chmod -R g+s
die entsprechenden Verzeichnisse mit dem SetGID-Bit versehen. Somit behalten alle Dateien, die inden Verzechnissen erzeugt werden die entsprechende Gruppenzugehörigkeit.
Das Problem der Berechtigungen haben wir schon gelöst. Die Angabe umask 027 im Shell-Script bewirkt, dass neue Dateien mit den entsprechenden Berechtigungen erzeugt werden.
Nun haben wir folgendes erreicht. Dateien gehören dem jeweiligen Benutzer und können so per PHP gelesen und geschrieben werden. Ebenso ist der FTP-Zugriff voll möglich. Die Gruppenzugehörigkeit liegt beim Apache-Server, somit können alle Dateien zwar per Apache gelesen werden, aber die PHP-Scripte, die unter der Gruppe “users” laufen, können nicht auf andere Verzeichnisse zugreifen.
Zusätzlich können wir sensible Daten, die für PHP-Scripte benötigt werden, in Dateien auslagern, die nur für den Benutzer lesbar sind. Somit kann das PHP des Benutzers zwar auf diese Datei zugreifen, Apache aber nicht mehr.
Alle anderen Schutzmechanismen kann der Nutzer mit .htaccess-Dateien selbst herstellen.
Ein Sicherheitsproblem besteht allerdings weiterhin. Es gibt standardmäßig eine Vielzahl von Dateien, die jeder lesen kann. Hier ist zu beachten, dass nicht jeder sie lesen muss. Eine sorgfältige Wahl der Berechtigungen, insbesondere vieler Konfigurationsdateien, ist hier die Lösung.
Größere Sicherheit bietet nur eine chroot-Umgebung, in der der Webserver abgeschlossen läuft.
Ich hoffe dieser Leitfaden hat euch ein wenig geholfen eure Webserver etwas sicherer zu machen.



August 8th, 2005 um 10:03
Hi,
ich hatte schon vor Monaten mal an folgendes gedacht:
Man sollte PHP mit open_basedir konfigurieren und PHP so patchen,
dass bei der Pruefung von open_basedir vor dem Ausfuehren des
Scriptes, der open_basedir_path immer mit der basedir
des Scriptes ergaenzt wird, sofern es zu einem
definiertem Wilcard passt.
Also open_basedir in der php.ini z.B. auf:
open_basedir = “/home/^:/tmp:/var/tmp”
Nehmen wir an, dass das Script eines virtualhosts gerade unter
/home/kundexy/website/index.php
liegt und dort aufgerufen werden soll.
PHP sieht die ^ und der open_basedir wildcard passt zur
Lage des Scriptes und wird freigeschaltet, sprich das Script
darf dann ueberall in /home/kundexy starten
und dort lesen/schreiben/aufrufen, sofern die sonstigen
Permissions es erlauben.
Somit wird also verhindert, dass ein Script bei Kunde
xy aufgerufen wird und im Bereich von kundex123
“fischen” geht (oder sonstwo im System).
Und mod_php kann weiter als nobody und als module laufen …
Ich habe mal einen Patch dafuer gemacht, aber der funktioniert
irgendwie nicht 100%.
ftp://ftp.powerweb.de/pub/php/php-dsh.patch
Vielleicht kann mal einer drueber gucken und das verbessern …
Gruesse, Frank – PowerWeb
August 8th, 2005 um 10:40
Hallo!
Mit der Idee würdest du allerdings lediglich dem Admiin ein wenig Schreibarbeit abnehmen, da ja die open_basedir auch manuell mit dem gleichen Ergebnis konfiguriert werden kann.
Du übersiehst bei deinem Ansatz allerdings, dass mit den Shell-Funktionen (exec, system, etc.) weiterhin fröhlich im System und vor allem in den Verzeichnissen anderer User rumspaziert werden kann.
Matthias
August 10th, 2005 um 00:23
Was ein nettes Howto!
Was die Filesystem Berechtigungen angeht, so kann man das auch einfacher ohne +s Bit lösen. Wenn alle Web User in der Gruppe users sind, reichen folgende Berechtigungen für das Home des jeweiligen Webs:
Besitzer : Web-User
Gruppe : users
Rechte Besitzer: rwx
Rechte Gruppe: (keine)
Rechte Andere: rx
Für den User der das Verzeichnis besitz, ist nach dem Prüfen der Besitzer Rechte schluss. Bei allen anderen Web Usern nach dem Prüfen der Gruppe users.
Da der Apache weder Besitzer noch in der Gruppe ist, gelten für ihn die Rechte für andere.
Zu Powerweb hab ich nur eins zu sagen, mod_php mit geteiltem Apache auf Shared Servern ist ganz ganz baba.
In der WHL hab ich noch gelesen das ihr für exec ein extra Dir habt wo ihr Sachen reinpackt die “sicher sind”, z.B. ImageMagick.
Dummerwiese kann man damit jede Text Datei als Grafik ausgeben lassen. Wenn man dann z.B. davon ausgeht, das in dem DocRoot eines Webs z.B. ein Typo3 liegt, kann man sich den Pfad zur typo3conf.php recht einfach zusammenstellen und schon hat man das Paßwort zur DB.
Aber selbst wenn man das Ausführen per system, exec ganz unterbindet, bleiben in PHP noch zig Libs wie z.B. curl über die ähnliches möglich ist.
Mein Fazit also: mod_php nur wenn man alleine auf dem Server ist oder je Web einen eigenen Apachen unter dem User des Webs laufen läßt. Ansonsten PHP per CGI oder auch FastCGI.
Was mich übrgens mal interessieren würde, wie ist das bei Confixx (Nie gesehen ober benutzt), wird da PHP als CGI ausgeführt, oder läuft es da als mod_php unter einem User für alle Webs?
MfG AbRaXeS
August 10th, 2005 um 09:55
Hi AbRaXeS,
einfacher und hinreichend sicher sollte dein Hinweis sein. Allerdings bevorzuge ich irgendwie eine Lösung ohne world-readable, auch wenn hier eine bestimmte Gruppe ausgeschlossen ist…
Grüße,
Matthias
August 10th, 2005 um 12:31
Hallo Matthias,
das Problem das ich bei dem Sticky Bit sehe sind eher die User, die das evt. mal für ein Dir verstellen und dadurch neu angelegte Verzeichnisse Dirs nicht mehr die passenden Berechtigungen haben. Der Apache liefert die dann nicht mehr aus und der User peilt es nicht.
Durch das world-readable können zwar im Prinzip alle User da dran die nicht in der Gruppe Users sind, nur was bleibt noch über, wenn man alle Kunden in der Gruppe hat. Nur noch Systemdienste wie Postfix und ähnlich, was ich nicht als kritisch ansehe, da die meisten Dienste eh als Root laufen.
Ich selbst setze übrigens auf mod_php, jedoch mit eigenem Apachen je Web, davor ein Proxy, der die Anfragen von einer IP an die jeweiligen Apachen unter de System Benutzern verteilt. Da kann man dann auch nur dem User Rechte am sienem Home geben. Das ganze hat aber den Nachteil, dass je Web ein eigner Apache läuft und Speicher braucht, dafür kann man neben mod_php auch andere Module wie mod_perl oder mod_ruby sicher laufen lassen und dem User mehr erlauben (Z.B. Per .htaccess AllowOveride All, ist ja sein Apache). Für Low-Cost Hosting ist das sicher keine Lösung, aber für Kunden die bereit sind dafür zu zahlen (Bzw. mehr als nur PHP möchten), sicher eine saubere Lösung.
MfG AbRaXeS
Juni 26th, 2006 um 17:42
Hab Dank für Dein howto! Das Setup funktioniert bei mir jedoch leider nicht. Ich verwende ein Gentoo Linux mit Apache2, mit diesen Einstellungen erhalte ich bloss Fehlermeldungen wie etwa:
[warn] FastCGI: (dynamic) server “/mnt/xulp/tests/tests/index.php” (pid 18557) terminated by calling exit with status ’114′
In der Dokumentation von Apache2 und suEXEC heisst es:
“…the target CGI/SSI program must reside within suEXEC’s document root (see –with-suexec-docroot=DIR below).”
Für einen Webserver, der mittels fastcgi und suEXEC php scripts ausführen soll (ist ja eher für einen Webserver mit mehreren users, also mit diversen virtual hosts interessant), finde ich das jedoch nicht sehr hilfreich. Das würde ja bedeuten, ich könnte die virtual hosts bloss während der Kompilierung des Apache mit einem einzigen doc-root konfigurieren. Genau das möchte ich ja nicht, denn ich möchte ja eben virtual hosts mit unterschiedlichen doc-roots, mehreren users mit mehreren Webseiten…
Mir scheint das Setup daher mit mod_fastcgi und suEXEC sehr schwierig und offen gestanden sehr unpraktisch.
Für einen Kommentar hierzu komme ich wahrscheinlich etwas spät, trotzdem wollte ich das noch loswerden, dieses howto scheint mir von allen das am ehesten nachvollziehbare, obwohl ich bis jetzt das ganze für meine Wünsche noch nicht erfolgreich umsetzen konnte. Eine Alternative bietet daher ein Setup mit virtual servers, so kann jeder vom unkomplizierten mod_php profitieren und sich trotzdem sicher gegen die restlichen users wissen.
Trotzdem Danke für die Einführung!
Grüsse
Dezember 25th, 2006 um 13:18
Die optimal Lösung für das Massenhosting-Problem ist eigentlich ein besserer Threadhandler wie zb. mod_perchild oder metuxmpm. Mit derartigen Threadhandlern laufen die kompletten Kindprozessse mit den Rechten der fürs Massenhosting benötigten Usern, d.h. man könnte weiterhin mod_php nutzen, was natürlich einen Performancevorteil bietet.
mod_perchild wurde aber schon aufgegeben, und metuxmpm ist leider noch nicht stabil, und dazu schlecht dokumentiert (http://mpm.metux.de).
Frohe Festtage