Wie programmiere ich sichere PHP-Scripte

Um sichere PHP Scripte zu programmieren, gibt es eine wichtige Grundregel:

Traue keinen Benutzereingaben !

Alle Daten, welche vom Benutzer eingegeben werden, können auch manipuliert sein. Daher sollten alle Benutzereingaben auf Gültigkeit geprüft werden. Lediglich in Umgebungen, bei welchen der Kreis der Benutzer klein ist und fest steht (Intranet / passwortgeschützter Zugang) kann man ggfs. auf diese Prüfungen verzichten.
Besonders kritisch sind dabei Daten, welche in SQL-Statements eingesetzt werden, welche mit dem mail() - Befehl verwendet werden, Daten, die als Argumente für Systembefehle wie system, ´´ (Backticks), exec, passthru, sowie auch Daten, welche das Dateiname für Dateien im System des Servers verwendet werden. Aber auch wenn Benutzerdaten „nur“ gespeichert und wieder ausgegeben werden, sind ohne Prüfung schon Cross-Site-Scripting Angriffe möglich.

Benutzerdaten sind dabei alle Daten, welche über den Browser an das Script gesendet werden. Neben URL-Parametern, Formulardaten und Cookies gehört hier z.B. auch der Referer dazu.

Auch wenn beispielsweise ein Script nur einen Link auf example.php?id=1&action=show für eine bestimmte Benutzergruppe ausgibt, kann es sein, das jemand das Script mit example.php?id=1&action=delete aufruft. Auch wenn in einem Formular ein Wert per hidden-Feld oder als Select-Liste dargestellt wird, ist es für einen Angreifer unproblematisch, diese Werte durch andere, nicht aufgeführte Werte zu ersetzen, dafür reicht schon die „Web Developer“ - Erweiterung von Firefox.

Eingabedaten daher grundsätzlich auf dem Server vor einer Verwendung prüfen !

  • Festgelegte Werte: in_array($eingabe, Array(„Wert1“,„Wert2“,„Wert3“))

  • Numerische Angaben: is_numeric($eingabe) (floats) oder is_numeric($eingabe) && (intval($eingabe)==$eingabe) (integer), ggfs. zusätzliche Prüfung auf minimalen und maximalen Wert

  • Texte:

  • Länge der Eingabe strlen

  • erlaubte/unerlaubte Zeichen, wie z.B. Zeilenumbrüche, Sonderzeichen preg_match

  • HTML-Tags strip_tags

  • JavaScript (falls HTML erlaubt) preg_replace

  • Nur bestimmte Angaben, wie URLs oder E-Mail Adressen PEAR Validate

Sind für Texte alle Zeichen erlaubt und sollen so ausgegeben werden, wie sie eingegebenen wurden, muss man mit htmlspecialchars oder htmlentities eingegebene HTML-Sonderzeichen in die entsprechenden HTML-Entitäten wie & lt ; umwandeln. Nur so ist gewährleistet, das HTML-Sonderzeichen wie „“ ausgegeben werden, anstatt als HTML-Code interpretiert zu werden.

Das Prüfen der Eingabedaten kann man natürlich auch einer Klasse wie HTML::Quickform überlassen, mit welcher man die gesamte Formularverarbeitung „organisieren“ kann.

Einige mögliche Angriffe:

SQL Injections

Werden Daten in SQL-Statements eingebaut, so sollten bei diesen alle Sonderzeichen, welche von der Datenbank „besonders“ interpretiert werden, entsprechend escaped werden.

mysql\_query("SELECT user\_id FROM users WHERE user='$eingabe\_user' AND passwd='$eingabe\_pass');

Sind in diesem Beispiel Sonderzeichen in der Eingabe erlaubt, könnte man dort beispielsweise als Passwort ’ OR ‚‘=’ eingeben, was ein WHERE user=‚eingabe‘ AND pass=’’ OR ‚‘=’’ ergibt. Durch die letzte Bedingung ist die Passwortprüfung immer erfolgreich.

Um dies zu vermeiden, Daten, welche in Datenbankabfragen verwendet werden, immer mit der entsprechenden Funktion zum Escapen der Sonderzeichen, z.B. mysql_real_escape_string für mySQL verwenden. Die PEAR DB / MDB2 Klassen kümmern sich ber der Verwendung von Prepare / Execute automatisch um das datenbankspezifische „behandeln“ der Sonderzeichen.

mail()

Bei der Verwendung der Mailfunktion ist es wichtig, Daten, welche als Subject oder als Header („From:“) verwendet werden, auf Sonderzeichen \r und \n (Zeilenumbrüche) zu prüfen. Angreifer senden gern als Subject oder Absendermail Text\nBcc: Mailadresse\nBcc:… Bei ungeprüftem Einsetzen von Subject und/oder Header lässt sich damit das Script zum Versenden vom Spam missbrauchen, da die zusätzlichen Bcc: Zeilen als zusätzliche Empfänger interpretiert werden.

Dateien

Werden Dateinamen aus Benutzereingaben verwendet, muss geprüft werden, ob der Dateiname zulässig ist. Bei einem include($eingabe) in einem Script kann man jede Datei auf dem Server auslesen, PHP-Code ausführen, bei aktiviertem allow_url_fopen sogar beliebigen PHP-Code von einem externen Server nachladen und ausführen.
Die Eingabe sollte im besten Fall gegen eine Liste der erlaubten Werte geprüft werden. Ansonsten sollte sichergestellt werden, das absolute oder relative Pfadangaben wie /etc/php.ini oder …/…/etc/php.ini nicht erlaubt sind. $file = preg_replace("#[^a-z0-9]+#","", $eingabe) entfernt alles außer a-z und 0-9 aus der Eingabe, ein Einbinden eines include mit include(„erlaubtes_verzeichnis/“.$file.".html") als Parameter ist damit schon wesentlich sicherer. Natürlich sollte man vorher noch mit is_file prüfen, ob es eine solche Datei gibt und eine entsprechende Fehlerbehandlung programmieren.

Systembefehle

Man sollte soweit wie möglich vermeiden, Benutzereingaben an Systemaufrufe (exec, system, ´´, passthru) zu übergeben. Ist dies doch unbedingt nötig, muss auf die entsprechenden Daten immer escapeshellcmd angewendet werden, um das Einschleusen von Schadcode zu vermeiden.

variabler Code

Analog zu den Systembefehlen gilt auch für Funtionen, die eine Eingabe als PHP-Code ausführen (eval, create_function): man sollte möglichst vermeiden, Benutzereingaben in solchen Aufrufen zu verwenden, und wenn es nötig sein sollte, dann nur sorgfältig geprüfte Eingaben.

Cross Site Scripting

Werden eingegebene Benutzerdaten an irgendeiner Stelle wieder ausgegeben, sollten diese, sofern die Sonderzeichen nicht mit htmlspecialchars „entschärft“ wurden, auf unerwünschten JavaScript Code geprüft werden. Einfachster Fall ist dabei, wenn keine oder nur einige HTML-Tags erlaubt sind, dann hilft strip_tags weiter. Soll HTML erlaubt sein (was selbst schon ein Problem sein kann, ein zusätzliches table-Tag kann das Layout einer Seite sehr ändern), kann man zumindest mit _preg_match("# eingebettete Scripte erkennen, mit <I><[^>]*#on.*?=[^>]*>#i</I> JavaScript Handler und mit <I>#<a[^>]*href="javascript:#</I> Javascript-URLs erkennen und abweisen.

<B>Links</B>
<A href="http://www.php.net/manual/de/security.php">Abschnitt „Sicherheit“ im PHP Handbuch</A>
<A href="http://php-faq.de/ch/ch-security.html">Abschnitt „Sicherheit“ in der PHP FAQ</A>_