Kurz gefasst
Dieses Dokument enthält eine kurze Prüfliste mit den häufigsten Schwachstellen von Web-Anwendungen und deren Lösungen, sowie bewährten Praktiken in der Webentwicklung. Obwohl im Folgenden die Skriptsprache PHP den Schwerpunkt bildet, können die meisten Empfehlungen auf andere Sprachen übertragen werden.
Veraltete PHP Einstellungen (php.ini)
Gefährliche und veraltete Einstellungen, die noch aus Gründen der Kompatibilität in PHP 5 existieren, aber standardmäßig deaktiviert sind (und sein sollen):
-
register_globals
-
magic_quotes_gpc
-
safe_mode
Überprüfung der Benutzereingabe
Folgende Ratschläge sind stets zu beachten:
-
Die systemweite PHP-Einstellung „error_reporting = E_ALL” erlaubt das Erkennen von nicht-initialisierten Variablen und sonstigem unsauberen Quelltext. Diese sehr detailierte Fehlerausgabe kann auch auf lokaler Ebene mit Hilfe von der Zeile „error_reporting(E_ALL);” erzielt werden. In einer Produktionsumgebung soll jedoch jegliche Fehlerausgabe verhindert werden („error_reporting = 0” und „display_errors = 0” in php.ini).
-
Der Gebrauch des Identitätsoperators „===“ (im Gegensatz zum Gleichheitsoperator „==“) verhindert Fehler, die unter anderem im Umgang mit Vergleichen mit bool’schen Werten auftreten. So gilt zum Beispiel „0 == false”, wohingegen „0 === false” unwahr ist.
-
Überprüfen Sie jede Benutzereingabe serverseitig. Besonders gut eignen sich dazu die Filterfunktionen, wie beispielsweise filter_var().
-
Vermeiden Sie den Gebrauch der Superglobale $_REQUEST, da diese Werte aus unterschiedlichen Quellen enthalten kann.
-
Prüfen Sie die Existenz der unterschiedlichen, erwarteten Benutzereingaben mit isset(), beispielsweise isset($_GET[‚id‘]).
-
Auch einige Felder in $_SERVER (insbesondere $_SERVER['HTTP_REFERER']) stellen manipulierbare Benutzereingaben dar. Analoges gilt für $FILES: ein mögliches Angriffsszenario ist der Upload einer Datei mit $_FILES[‘file’][‘name’]=’../../../etc/passwd’. Hier empfiehlt sich der Gebrauch der Funktion basename().
-
Prüfen Sie den Inhalt einer hochgeladenen Datei. Bilder lassen sich sehr gut mit der Funktion getimagesize() überprüfen, die „false” zurückgibt, falls es sich nicht um eine Bilddatei handelt. Beachten Sie ebenfalls die Erweiterung fileinfo.
-
Verwenden Sie niemals serialisierte Objekte als Eingabe.
Generell empfiehlt sich das Konzept der „weißen Liste” für die Prüfung der Benutzereingabe (verwerfe alles was nicht ein konkretes Format besitzt).
Zusätzlichen Schutz bieten die Erweiterungen „PHP Input Filter” und „PHPIDS”, sowie die Apache-Erweiterung „mod_security”.
SQL-Code-Einschleusung (SQL injection)
Die Verwendung von PHP-Variablen in SQL-Abfragestrings ist unter allen Umständen zu vermeiden, da so sehr leicht ungewollter SQL-Code ausgeführt werden kann.
-
Benutzen Sie parametrisierte Abfragen (prepared statements). In PHP führt sich die Maskierung der Sonderzeichen in Variablen am einfachsten mit Hilfe einer Abstraktionsebene wie PDO durch.
-
Die parametrisierbaren Abfragen können auch durch PHP-Basis-Funktionen der verschiedenen Datenbanken realisiert werden (für PostgreSQL, zum Beispiel, siehe pg_prepare()).
-
Falls das Aneinanderhängen von Variablen unumgänglich ist, muss die Maskierung der Sonderzeichen in Variablen mit Funktionen vom Typ „(real_)escape_string“ erfolgen. Im Fall von MySQL informieren Sie sich über mysql_real_escape_string(). Es ist anzumerken, dass vom Gebrauch dieser Funktionen abgeraten ist. Sie stellen sich jedoch als durchaus nützlich heraus, wenn veraltete Applikationen schnell und einfach abgesichert werden sollen.
-
Vermeiden Sie um jeden Preis automatisierte Techniken wie magic_quotes oder generische Funktionen wie add_slashes(). Jeder Datenbanktyp reagiert verschieden (und möglicherweise fehlerhaft oder unvollständig) auf diese Maskierungsangehensweisen.
Cross-site scripting (XSS)
XSS besteht in der Eingabe von Javascript, das dann beim Anzeigen ausgeführt wird. Es handelt sich um eine sehr bekannte und gefährliche Schwachstelle, die in vielen Fällen einfach zu vermeiden ist.
Sie finden eine komplette Liste auf der Webseite von OWASP.
-
Transformieren Sie alle Zeichen der anzuzeigenden Variablen in HTML-Entitäten. In PHP genügt ein Aufruf der Funktion htmlentities(). Verschiedene Template-Engines (wie etwa „flexy”) führen diese Maskierung automatisch durch.
-
Falls Variablen tatsächlich HTML-Code beinhalten dürfen, kann die Funktion strip_tags benutzt werden. Diese ist jedoch mit Vorsicht zu verwenden, da möglicherweise ebenfalls erlaubte Tags, wie etwa Beispiel : <img onmouseover="alert(‘XSS’);" src="" /> oder <a href="javascript:alert(‘XSS’);">Link</a>, von XSS betroffen sind. In dem Fall müssen eigene Filter zusätzlich zu strip_tags() geschrieben werden.
-
Die Klasse htmlpurifier (http://htmlpurifier.org, GPL-Lizenz) bietet diese Funktionen.
Im Allgemeinen muss man jedoch eine generische Anzeigefunktion schreiben, die bei jedem Seitenaufruf benutzt werden muss.
Einschleusung von Quelltext (code injection)
Injektion von schädlichem Code, der von der Anwendung ausgeführt wird.
-
Die Verwendung von Variablen in den Befehlen “include” oder “require” soll unbedingt vermieden werden. Falls dies nicht möglich ist, soll man die Verwendung von Whitelist-Filtern in Erwägung ziehen.
-
Vermeiden Sie unter allen Umständen die Funktion eval(). Alternative Vorgehensweisen sind beispielsweise variable Variablen (http://www.php.net/manual/de/language.variables.variable.php), Closures (http://www.php.net/manual/de/functions.anonymous.php) und die Funktion call_user_func().
-
Deaktivieren Sie die PHP-Einstellung (in php.ini) allow_url_fopen, falls diese nicht benötigt wird.
-
Vermeiden Sie Variablen im Umgang mit der Funktion preg_replace().
Einschleusung von Befehlen (command injection)
Einige Funktionen erlauben das Ausführen von System-Befehlen.
-
Vermeiden Sie Variablen in den Argumenten der Funktionen shell_exec(), exec(), system(), passthru() und popen(). Falls dies unumgänglich ist, verwenden Sie unbedingt die Funktionen escapeshellarg() und escapeshellcmd().
-
Benutzen Sie Whitelists für Dateinamen.
-
Ziehen Sie den Gebrauch der Funktion basename() in Erwägung.
Sicherheit von Sitzungen
Es ist möglich, die Sitzung anderer Benutzer zu verwenden, indem man die Sitzungsnummer stiehlt. Siehe beispielsweise „firesheep”.
-
Vermeiden Sie die Angabe der Session-ID in der Adresse („?PHPSESSID=123”), da andere Webseiten beispielsweise den Referer auslesen können. Verwenden Sie daher die PHP-Einstellung „use_only_cookies = 1” in php.ini.
-
Verwenden Sie ausschließlich SSL/TLS-Verbindungen, da ansonsten das Sessions-Cookie von Dritten ausgelesen werden kann. Erzwingen Sie den verschlüsselten Gebrauch des Cookies mit „session.cookie_secure = 1”.
-
Definieren Sie einen Ablaufdatum für Sitzungen.
-
Assoziieren Sie Sitzungen mit IP-Adressen.
Cross-Site Request Forgery (XSRF)
XSRF besteht darin, dass eine offene Sitzung durch eine böswillige Webseite in einem anderen Tab manipuliert wird.
Meistens sind es Formulare, die von einer böswilligen Webseite ausgefüllt werden können. In diesen konkreten Fällen kann man das Problem dadurch umgehen, dass man einen “unique identifier” fürs Formular definiert und dieses bei der Einreichung abfragt. Es hat sich bewährt, einen durch eine Funktion des Typs uniqid() oder einen mit einem Passwort gehashte Pseudozufallszahl durch die Funktion hash_hmac() erzeugten Code zu verwenden, den man in einem versteckten Feld im Formular sowie in der Session ($_SESSION) verwendet, und welchen man dann bei der Abgabe des Formulars vergleicht.
Um Funktionen, die keine Formulare umfassen, zu schützen, bedarf es einer Fall-zu-Fall-Analyse.
Leitfaden zum Speichern von Benutzer-Passwörtern in der Datenbank
Passwörter müssen gehasht in der Datenbank gespeichert werden. Hierzu sollte man keinesfalls Funktionen des Typs “md5“ oder “sha1“ benutzen, da es für diese “rainbow tables” gibt, die es erlauben, schnell vom Hash auf das Passwort zu gelangen.
Um den Angriff mit Hilfe von “rainbow tables” zu verhindern, und um gleiche Hash-Werte (da das zugrunde liegende Passwort gleich ist) zu maskieren, empfiehlt es sich, sogenannte „salts” zu verwenden. Ein “salt” ist ein zufällig generierter String und wird vor dem Hashing an das Passwort angehängt. Anschließend speichert man den salt sowie das gehashte Passwort in der Datenbank.
Es ist auch ratsam, zusätzlich dazu einen konstanten, aber geheimen “salt” zu verwenden, der nicht in der Datenbank gespeichert wird, sondern zum Beispiel in einer Konfigurationsdatei.
Anstatt einen „salt” an das Passwort anzuhängen, kann auch die Hashfunktion hash_hmac() verwendet werden, wir raten jedoch zur Verwendung der Funktion “crypt”, die einen Komplexitätsfaktor hinzufügt, falls man “CRYPT_BLOWFISH”, “CRYPT_SHA256” oder “CRYPT_SHA512” (siehe “key stretching”) verwendet. Seit PHP 5.5 vereinfachen die Funktionen password_hash() und password_verify() diese Vorgehensweise.
Verwenden Sie zum Generieren zufälliger Bytes kryptografisch sichere Funktionen wie openssl_random_pseudo_bytes(), und nicht etwa (mt_)rand()!
Die Hash-Berechnung funktioniert beispielsweise wie folgt:
$hash = crypt(hash_hmac(‘sha512’, ‘Passwort’, ’configsalt’), '$2a$'.$cost.'$dbsalt$'));
$hash = password_hash(hash_hmac(‘sha512’, ‘Passwort’, ’configsalt’), PASSWORD_BCRYPT, ['cost'=>$cost]); // seit PHP 5.5
Die Variable $cost muss so angepasst werden (durch Ausprobieren), dass die Berechnungsdauer für Ihre Anwendung akzeptabel ist (0.01 Sekunden zum Beispiel) , aber zeitgleich einen Brute-Force-Angriff verlangsamt.
Benutzung von Gegenmaßnahmen
Im Allgemeinen wird eine Anwendung vor einem Angriff durch den Angreifer mit einem automatischem Tool gescannt. Man kann also seine Anwendung mit Fallen oder anderen “tar pits” versehen. Es existieren einige Tools, die dies bewerkstelligen, so zum Beispiel weblabyrinth (http://www.mayhemiclabs.com/content/new-tool-weblabyrinth). Allerdings können schon einfache Maßnahmen wie die sleep()-Funktionen oder ein augenscheinlich richtiges Resultat bei einer falschen Authentifizierung bereits ausreichen.
Webroot != Approot
Bibliotheken und Scripts, die nicht unmittelbar vom Benutzer aufgerufen werden, sollten sich in einem nicht von außen aufrufbaren Ordner befinden. So können eventuell vorhandene Schwachstellen ebenjener nicht direkt ausgenutzt werden.
Denial of service
Es ist unmöglich, sich absolut gegen Denial-of-service-Angriffe zu schützen. Um solchen Angriffen entgegenzuwirken, kann man allerdings versuchen, die Antwortzeiten allgemein zu verringern. Die Erläuterung der verschiedenen Strategien sprengt den Rahmen dieses Dokuments, im Folgenden werden jedoch die wichtigsten Empfehlungen aufgelistet.
-
Vermeiden Sie die Ausgabe einer großer Menge an Daten aus einer Datenbank, z.B. in foreach-Schleifen. Verwenden Sie Limiterungsausdrücke wie LIMIT in MySQL.
-
Benutzen Sie kompilierte Cache-Engines (memcache, eaccelerator, Zend platform).
-
Debugging-Tools können kritische Pfade aufzeigen (xdebug, yslow, firebug).
-
Komprimieren Sie statische Inhalte (u.a. auch Javascript- und CSS-Code).
-
Durchsuchen Sie ihre Anwendung nach langsamen Datenbankabfragen (mysql_slow_query()).
-
Komprimieren Sie ggf. die Übertragungen zwischen Server und Client (erfordert genügend Rechenleistung).
Das Apache-Modul mod_evasive erkennt und blockiert Angriffe automatisch.
Frameworks
Viele Frameworks werden von erfahrenen Entwicklern geschrieben und sind von deren langjähriger Erfahrung gepägt. Dies beschränkt sich nicht nur auf die Sicherheit des Quelltextes, sondern beinhaltet auch gute Programmiertechniken, die ein schnelleres und sichereres Entwickeln von Web-Anwendungen ermöglicht. Beispiele für solche Frameworks sind Zend oder Symfony.
Ratschläge für php.ini
Die PHP-Einstellung (php.ini) „open_basedir” schränkt den Zugriff der PHP-Anwendungen auf bestimmte Verzeichnisse ein, so dass Schwachstellen nicht dazu ausgenutzt werden können, das restliche System zu kompromittieren.
Die folgenden Einstellungen sollten den Bedürfnissen der Anwendung angepasst werden, wobei die Werte so niedrig wie nötig zu halten sind. Generell ist es vorteilhaft, diese Parameter lokal für jede Anwendung, die mehr Ressourcen benötigt, anzupassen (unter Benutzung der Funktion ini_set()).
-
max_execution_time
-
memory_limit
-
post_max_size
-
upload_max_filesize
Im Produktionsumfeld ist es ratsam, keine Fehler anzuzeigen, da diese dem Angreifer zusätzliche Informationen geben können.
-
display_errors = Off
