Das erstellen von Webseiten per PHP ist standardmäßig nicht für jedermann aktiviert. Der Grund dafür ist hauptsächlich, dass durch PHP nicht unbeträchtliche Sicherheitsrisiken entstehen. Aus diesem Grund erwarten wir von dir dass du dich über gängige Sicherheitsprobleme informierst und diesen entsprechend vorbeugst.
Auf dieser Seite haben wir einige Stichworte zusammengetragen. Die Liste erhebt keinen Anspruch auf Vollständigkeit, liefert aber eine ganz gute Übersicht über die häufigsten Probleme, die auftreten können. Wir gehen davon aus, dass du dich selbständig weiter informierst. Insbesondere solltest du dir das Kapitel über Sicherheit aus der PHP-Anleitung durchlesen.
Wir überprüfen unregelmäßig die auf unserem Server liegenden PHP-Skripte auf offensichtliche Sicherheitsmängel und mahnen diese entsprechend an. Sollten die Probleme nicht behoben werden oder wiederholt auftreten werden wir unter Umständen deine Seite abschalten.
Wir können natürlich nicht Zeile für Zeile jeden Code durchgehen. Nur weil wir uns ein Skript mal angesehen haben heißt das noch lange nicht, dass es jetzt wirklich Fehlerfrei ist. Dafür bist allein du verantwortlich!
Die Ausrede, deine Seite sei erstmal zum testen und du würdest sie später sichern wollen zählt nicht. Alles, was jemals ins Netz gestellt wird - auch nur für 10 Minuten - muss im Hinblick auf Sicherheit programmiert worden sein. Aus diesem Grund ist es sinnvoll, die Skripte zuerst auf einem anderen Rechner oder mit dem PHP-Interpreter von Hand zu testen bevor du das fertige Ergebnis veröffentlichst.
Du solltest, falls möglich, immer mehrfache Sicherheitsvorkehrungen treffen. Nur weil du glaubst, dass du eine Gefahr schon abgewendet hast, heisst das noch lange nicht, dass dem auch tatsächlich so ist. Es ist deshalb immer ratsam, mehrere Sicherheitsstufen einzubauen.
Immer wieder haben es findige Angreifer geschafft, ein Sicherheitskonzept auszuhebeln. Wenn dann aber noch ein zweites da ist, wird es deutlich schwieriger dieses auszunutzen. Und wenn auch dieses durchbrochen wurde, sorg vielleicht ein drittes dafür, dass sich der Schaden in Grenzen hält.
So sollte die Oberfläche deiner Anwendung, die mit dem Benutzer kommuniziert bereits alle Ein- und Ausgaben überprüfen und entsprechnde Fehlermeldungen generieren. Die Routinen, die dann die Verarbeitung übernehmen (z.B. Datei- oder Datenbankzugriffe) überprüfen die ihnen übergebenen Daten dann ein weiteres mal und brechen gegebenenfalls ab. Als dritte Sicherheitsvorkehrung könnten Unix-Dateirechte oder Datenbank-Zugriffsrechte dienen.
Du solltest niemals glauben, dass ein bestimmter Fehler zwar theoretisch ausgenutzt werden kann, aber eigentlich harmlos ist. Immer wieder lassen sich 2-3 eigentlich harmlose Fehler zu einer bösartigen Lücke kombinieren und ausnutzen.
Wenn du Include Dateien in einem offentlich zugänglichen Verzeichnis (also unter public_html) ablegst, können Angreifer diese auch direkt aufrufen. Aller code, der nicht in einer funktion steht wird dann auch ausgeführt. Das kann unter umständen fatale Folgen haben.
Aus diesem Grund ist es ratsam, die Include Dateien nicht in public_html zu veröffentlichen, sondern sie in einem privaten Verzeichnis abzulegen. Dann kannst du sie beispielsweise mit include "../include/zeug.php"; einbinden.
Die größte Gefahr geht immer von den Daten aus, die dein Skript aus dem Internet empfängt. Das sind vor allem Formulareingaben und Cookies. Aber auch Angaben wie die Browserkennung des anderen oder gar sein Hostname können gefährlich sein.
Deshalb ist es wichtig, dass du alle Daten überprüfst, die von außen an dein Skript übergeben werden. Fast alle Angriffe auf PHP-Skripte sind dadurch möglich, dass irgendwo ein string angenommen und verarbeitet wird ohne ihn auf bösartigen Inhalt zu überprüfen.
Wie bösartige Eingaben aussehen variiert von Situation zu Situation und es ist praktisch unmöglich, alle Möglichkeiten aufzuzählen. Generell ist es ratsam, Eingaben nicht daraufhin zu überprüfen, ob sie bösartig sind (Negativliste), sondern darauf, ob sie garantiert gutartig sind (Positivliste).
Wenn du beispielsweise einen Benutzernamen entgegen nimmst, so kannst du beispielsweise festlegen, dass ein Benutzername 3-15 Zeichen lang sein soll und nur aus kleinen Buchstaben und Zahlen bestehen darf. Das kannst du dann mit einem Ausdruck wie dem folgenden prüfen:
if ( ereg('^[a-z0-9]+$',$user) && (strlen($user)>=3) && (strlen($user)<=15) )Eine andere Möglichkeit ist es, bösartige Eingaben zu entschärfen. Hierzu gibt es je nach Situation verschiedene Methoden, meist benutzen Sie die Funktionen htmlentities(), mysql_real_escape_string(), urlencode() oder deren jeweiligen Verwandten.
Daten, die potentiell bösartig sind, sollten direkt vor dem jeweiligen Verwenden nochmal überprüft werden. Wenn du also einen Usernamen entgegen nimmst, so sollte deine Anwendung ganz normal die Fehlerbehandlung durchführen und vernünftige Fehlermeldungen ausgeben. Aber direkt vor der eigentlichen Verwendung (z.B. Datenbankabfrage) solltest du den Benutzernamen nochmals überprüfen und dein Skript gegebenenfalls "notabschalten".
Am besten ist es, du schreibst dir Funktionen, die das Überprüfen derartiger Daten übernehmen. Dann kannst du beispielsweise eine Datenbankabfrage wie folgt sichern:
function checkuser($user){
return ( ereg('^[a-z0-9]+$',$user) && (strlen($user)>=3) && (strlen($user)<=15) );
}
function eineDatenbankOperation($user){
checkuser($user) or die();
$result=mysql_query("SELECT FROM tabelle WHERE user='$user'");
...
}
Hier ist sofort offensichtlich, dass nochmal alle Daten überprüft worden sind und das schlimmste, was jemand schaffen kann ist, dass sich das Skript beendet - Sowieso nicht das schlechteste, wenn jemand angreifen will.
Die Fehlermeldungen von PHP können einem Angreifer viel über den internen Aufbau eines Skriptes verraten. Wenn ein Skript also anfällig sein sollte, so sind Fehlermeldungen eine hervorragende Hilfe für den Angreifer um Informationen über die genauen Angriffsmöglichkeiten zu finden. Aus diesem Grund sind die PHP-Fehlermeldungen per Default abgestellt.
Da derartige Meldungen beim programmieren aber teilweise benötigt werden um einen Fehler zu finden, kannst du die Fehleranzeige von Hand wieder anstellen. Führe einfach diese Befehle am Anfang deines Skriptes aus:
error_reporting(E_ALL);
ini_set("display_errors","on");
ini_set("display_startup_errors","on");
Für den Fall, dass es sich um einen Syntaxfehler handelt, der bereits beim Parsen auftritt, hilft diese Methode jedoch nicht, da ja der Fehler passiert bevor error_reporting aufgerufen wird.
In diesem Fall hilft jedoch ein kleiner Trick weiter: Schreibe einfach ein kleines Wrapperskript, welches zuerst error_reporting aufruft und dann mittels include das eigentliche Skript einbindet. Vergesse dann aber nicht, den Wrapper wieder zu löschen wenn du den Fehler gefunden hast!
Unter Cross Site Scripting versteht man einen Angriff, der daraus besteht, eine fremde Seite dazu zu veranlassen, ein selbst geschriebenes Skript (meist Javascript) an den Browser des Benutzers zu schicken. Die Tatsache, dass ein solches Skript von einem fremden Server zu kommen scheint kann man sehr oft ausnutzen. Hierzu gibt es ein detailierte Beschreibung.
Angenommen, dein Skript enthält einfach die Zeile
echo _GET["benutzername"];Ein Angreifer kann nun dem Opfer einen präparierten Link unterschieben und dafür sorgen, dass der Parameter benutzername nicht mehr ein harmloses Wort ist, sondern vielmehr aus einem Skript besteht: <script>code_der_cookies_klaut();</script>
Webbrowser verbieten es normalerweise, dass Skripte von einer Domain auf Ressourcen aus einer anderen Domain zugreifen können. Im obigen Beispiel wurde auf Cookies zugegriffen. Das fatale ist nun, dass ein Angreifer ein Skript einschleusen kann, von dem der Browser denkt, es käme von dir.
Wenn deine Website anfällig ist für Cross Site Scripting, gefährdest du nicht nur deine eigene Seite, sondern alle Seiten in der Domain. In diesem Fall wäre das alles unter *.uni-freiburg.de.
Aus diesem Grund ist es wichtig, dass du Ausgaben, die du an den Browser zurück gibst mit der gleichen Sorgfalt überprüfst wie alle anderen Daten, die du vom Benutzer bekommst oder sie mit htmlentities oder einem ähnlichen Befehl harmlos machst. Die Zeile von oben kann ganz einfach gesichert werden:
echo htmlentities(_GET["benutzername"]);Strings, die verwendet werden um eine Datei zu öffnen müssen ebenfalls mit besonderer Vorsicht genossen werden. Zunächst scheint es sicher eine Zeile wie die folgende zu schreiben:
readfile('/home/benutzername/bilder/'._GET["bildname"]);Scheinbar kann man hiermit nur Dateien aus dem Verzeichnis bilder lesen. Das ist aber leider nicht richtig. So kann ein Angreifer als bildname den String ../dateiname.txt angeben und nach diesem Muster beliebige Dateien lesen.
Wannimmer du eine aus dem Internet gegebene Variable verwenden willst um auf Dateien zuzugreifen, solltest du diese immer anhand einer Positivliste überprüfen.
Auch Anweisungen wie include oder require_once sind Dateioperationen, die extra überprüft werden müssen!
Weitere Informationen hierzu findest du auch in der PHP-Anleitung.
Wenn du einen String ungeprüft in eine SQL-Anfrage einbaust, kann dieser verwendet werden um der Abfrage eine komplett neue Bedeutung zu geben. Ein Beispiel:
$user=_GET['user'];
mysql_query("SELECT FROM tabelle WHERE user=\"$user\"");
Ein Angreifer kann dies ausnutzen, indem er als user nicht ein harmloses Wort einbaut, sondern folgendes ich"; UPDATE ...
Der Angreifer hat hier die Abfrage mit einem ; beendet und kann einen weiteren Befehl nach seinem belieben ausführen.
Aus diesem Grund müssen alle Daten, die an eine Datenbank übergeben werden anhand von Positivlisten überprüft werden oder mit mysql_real_escape_string oder ähnlichen Funktionen unschädlich gemacht werden.
Wenn du mysql_real_escape_string verwendest, solltest du beachten, dass du die Daten beim Lesen wieder mit mit stripslashes zurück konvertieren musst. Danach sind die Daten wieder als gefährlich anzusehen. Du darfst sie z.B. nicht einfach an den Browser senden, da du dadurch wieder eine Cross Site Scripting Attacke ermöglichen würdest.
Weitere Informationen dazu findest du auch in der PHP-Anleitung.
Du solltest deiner Anwendung möglichst wenig Rechte in der Datenbank einräumen. Rechte in der Datenbank können Spaltenweise vergeben werden. Das heisst, dass du wenn es nötig ist, nichtmal auf die ganze Tabelle Zugriff gewähren musst.
Wenn wir dir eine Datenbank anlegen, geben wir dir in der Regel Vollzugriff auf diese. Wenn du weisst, was deine Anwendung braucht, kannst du diese Rechte einschränken, bzw. von uns einschränken lassen. Auch geben wir dir gerne mehrere Benutzerkennungen mit unterschiedlichen Rechten.
Mehr Informationen zum Thema Datenbanken und Sicherheit findest du auch in der PHP-Anleitung.
Wenn du Cookies verwendest, solltest du immer einen Pfad und eine Domain setzen. Der Pfad sollte dabei so genau wie möglich sein. (z.B. /~benutzername/projekt/) und die Domain sollte immer cip.physik.uni-freiburg.de sein. Dadurch stellst du sicher, dass deine Cookies immer nur an deine Seite geschickt werden und nicht plötzlich anderen Seiten zur Verfügung gestellt werden.
Beachte, dass Daten, die du aus Cookies liest als feindlich anzusehen sind. Es scheint zwar so, dass alle Daten, die du aus Cookies bekommst auch von dir stammen, aber das stimmt nicht. Ein Angreifer kann die Daten, die in einem Cookie gespeichert sind nach belieben ändern. Du musst somit Cookiedaten mit der selben Sorgfalt behandeln wie alle anderen Benutzereingaben auch.
Wenn Du Sessions verwendest, solltest du stets session_set_cookie_params verwenden um Pfad und Domain zu setzen. (Siehe Abschnitt über Cookies). Außerdem solltest du mit session_save_path ein eigenes Verzeichnis angeben, in dem deine Sessioninformationen gespeichert werden. Wenn mehrere Leute die Standardeinstellung verwenden, kann es zu Überschneidungen und sehr lästigen Fehlern kommen.
Du brauchst dir bei PHP keine Sorgen darüber machen, dass ein Angreifer die SessionID vorhersagen kann. Darum kümmert sich PHP schon selbst.
Ein Angreifer kann jedoch unter Umständen eine SessionID klauen. Dies wäre beispielsweise mit Cross Site Scripting deiner Anwendung möglich. Wenn du mit Pfad und Domain den Geltungsbereich des Cookies nicht richtig eingeschränkt hast, können auch andere Seiten im Geltungsbereich die Session klauen. Entweder gehört dem Angreifer die andere Seite oder er kann die andere Seite mit Cross Site Scripting ausnutzen.
Eine relativ neue Angriffsmöglichkeit ist die Session Fixation Attacke. Diese besteht daraus, dass ein Angreifer einem Benutzer eine bestimmte SessionID unterschiebt. Somit muss er die SessionID gar nicht erst klauen. Es gibt eine detaillierte Beschreibung wie ein solcher Angriff ablaufen kann.
Um dem Vorzubeugen solltest du folgende Maßnahmen treffen:
session_start();
// An dieser Stelle eventuell noch andere Dinge tun falls notwendig.
session_unset();
session_destroy();
session_save_path('/home/benutzername/php_sessions');
session_set_cookie_params(60*60,'/~benutzername/anwendung/',
'cip.physik.uni-freiburg.de');
session_start();
session_regenerate_id();
Oft ist es wünschenswert, dass eine PHP-Anwendung Emails verschickt. Wenn es nun möglich ist, beliebigen Leuten Emails zu schicken, wird dies häufig von Spammern ausgenutzt. Dem kannst du aber relativ einfach vorbeugen.
Wenn dein Skript Emails nur an dich zu schicken braucht, gebe deine Emailadresse fest im Quelltext ein. Dann kann jemand höchstens dir Spam schicken, aber der Rest des Internet wird verschont.
Beschränke die Menge der Emails, die jemand versenden kann. Es ist im Normalfall mehr als ausreichend, dass ein Benutzer 10 Emails pro Stunde verschicken kann, meist sind sogar weniger angemessen. Du musst somit die absendende IP jeder Email speichern und einen Zähler und Zeitstempel installieren. Das speicherst du dann in einer Datei und jedes mal, wenn jemand eine Email verschicken will, schaust du nach, ob diese Person das noch darf.
Du darfst diesen Zähler natürlich nicht in der Session speichern, da die IP ja nicht einer bestimmten Session zugeordnet ist. Neue Sessions kann man sich im Normalfall nach belieben erzeugen.
Unter PHP kannst du auch fremde Seiten aufrufen. So wäre beispielsweise ein Skript denkbar, in das der Benutzer per Fomular eine URL eingibt und diese wird dann von deinem Skript geladen und verarbeitet.
Ein solches Skript birgt jedoch ein gewaltiges Mißbrauchspotential. Du erlaubst nämlich damit anderen, anonym und mit einer IP-Adresse des Pools im Internet zu surfen.
Das ist vor allem deshalb problematisch, weil häufig Webseiten beispielsweise nur für das Universitätsnetz freigegeben sind. Du würdest also einem Fremden ermöglichen, auf diese Webseiten zuzugreifen.
Ein weiteres Risiko ist, dass zum öffnen fremder Websites unter PHP die selben Routinen verwendet werden wir für lokale Dateien. Ein Angreifer können dies ausnutzen und anfangen lokale Daten abzurufen.
Aus diesen Gründen solltest du Zugriffe auf fremde Webseiten auf ganz wenige Seiten beschränken und deren Adressen fest in deinen Quelltext einbauen.
Shellaufrufe aus PHP sind hier im Pool gesperrt. Somit ist dieser Abschnitt eigentlich irrelevant. Solltest du aber mal ein Projekt ausserhalb des Pools verwenden wollen, kann es durchaus sein, dass es für dich von Bedeutung ist.
Häufig werden von einem PHP-Skript aus andere Programme aufgerufen. Wann immer du anderen Programmen Daten auf der Kommandozeile übergibst, musst du diese unbedingt überprüfen. Sonst kann beispielsweise einfach jemand einen ";" oder ein anderes für die Shell relevantes Sonderzeichen verwenden und den Befehl manipulieren.
Shellaufrufe prüfst du am besten mit einer Positivliste ab. Es gibt zwar Funktionen wie escapeshellcmd oder escapeshellarg, aber auch auf diese haben noch Fallstricke. So musst du beispielsweise bei escapeshellcmd selbst das Leerzeichen korrekt behandeln.