5 Gefahrenquellen in PHP-Anwendungen

PHP-Anwendungen sind bekannt dafür viele Sicherheitsmängel zu beinhalten (obwohl das nicht stimmt), trotzdem möchte ich heute einmal auf 5 der schwersten Sicherheitslücken aufmerksam machen. Schaut euch doch mal eure (älteren) PHP-Skripte an. Die meisten werden mindestens eine dieser 5 möglichen Gefahrenquellen wiederfinden 😉

  1. Daten, die aus der Datenbank kommen, sind sicher
    Grundsätzlich sollte auch die Datenbank als „Benutzer“ betrachtet werden und auch hier alle eingehenden Daten überprüft werden (z.B. wenn die Daten zur Generierung von neuen PHP-Code oder dem Laden von Dateien genutzt wird).
    Der Grund ist ganz einfach: Sollte ein Unbefugter Zugriff auf die Datenbank erlangen, könnte er damit evtl. auch direkt das Verhalten der PHP-Anwendung beeinflussen.
  2. Prepared Statements oder Escaping machen SQL-Injections unmöglich
    Über Best Practices gegen SQL-Injections habe ich bereits berichtet. Es reicht nicht aus nur Spaltenwerte mit Prepared Statements zu schützen. Auch Spalten-, Tabellennamen und alles was sonst noch vom Benutzer zur Datenbank gelangt, kann eine SQL-Injection enthalten.
  3. Session IDs, die in Cookies abgelegt sind, können nicht ausgelesen werden
    Das Standard-Verzeichnis für PHP-Sessions lässt sich von jedem, der ebenfalls einen Host auf dem Server hat, auslesen – ein Kinderspiel um an die IDs der Sessions zu gelangen. Verhindern lässt sich das am besten, in dem man ständig die IP-Adresse überprüft der Session abfragt, die Session-ID jedes mal gewechselt wird (session_regenerate_id()), oder man ein anderes Verzeichnis wählt (session_save_path()).
  4. Zugangsdaten zur Datenbank werden am sichersten in einer PHP-Datei abgelegt
    Die Aussage an sich ist natürlich völlig richtig, aber was passiert wenn die Datenbank plötzlich einen Fehler meldet und die Zugangsdaten über eine unglücklich programmierte Fehlerbehandlung zu Debugging-Zwecken ausgegeben wird?
  5. Mit MD5 verschlüsselte Passwörter sind sicher
    MD5-Hashes lassen sich mittlerweile kinderleicht über Hash-Datenbanken oder Rainbow-Tables knacken. Abhilfe schaffen Salted Passwörter oder verbesserte Verschlüsselungsmethoden wie beispielsweise RSA.

Nicht alle der fünf hier genannten Punkte sind selbstverständlich und manchmal denkt man auch nicht daran, dass so etwas ausgenutzt werden könnte. Daher macht es Sinn sich einmal selbst in die Rolle des Angreifers zu versetzten und zu versuchen seine eigene Anwendung mit allen Möglichkeiten zu hacken. Immerhin kennt man sich selbst mit seinem selbstgeschriebenen Code noch am besten aus.

PHP > SQL-Injection > Best Practice

Zur Beginn erst noch eine kleine Einführung in SQL-Injections.

Was ist eine SQL-Injection?
Wenn Jemand versucht eine Benutzereingabe so zu manipulieren, dass der generierte SQL-Befehl etwas anderes tut, als er soll. Nehmen wir mal folgendes Szenario an: Wir schicken eine URL an unseren WordPress-Blog, die einen zusätzlichen SQL-Befehl ausführt – nämlich die Tabelle xyz löschen.

http://web-union.de/?id=1; DROP TABLE xyz; --

Das generierte SQL-Query würde dann so aussehen:

$sql = "SELECT * FROM posts WHERE id = '{$_GET['id']}'";
SELECT * FROM posts WHERE id = '1'; DROP TABLE xyz; --';

Mit den letzten beiden Strichen wird das schließenden Anführungszeichen und das Semikolon am Ende der SQL-Query einfach auskommentiert.

Und wie lassen sich SQL-Injections effektiv verhindern?
Dazu gibt es einige Methoden, die ich unter anderem auch für Yumee verwende.

1. Benutzereingaben escapen
Das ist relativ schnell gemacht und sollte im Normalfall genug Schutz gegen Angreifer bieten. Ein blinder Angriff wird somit eigentlich ausgeschlossen.

$_GET = array_map('mysql_real_escape_string', $_GET);
$_POST = array_map('mysql_real_escape_string', $_POST);
$_COOKIE = array_map('mysql_real_escape_string', $_COOKIE);

Wer möchte kann so zusätzlich noch $_REQUEST und $_FILES escapen.

Damit diese Methode wirklich sicher funktioniert, muss bei der Verwendung noch einiges beachtet werden:

  • Die Benutzereingaben innerhalb der SQL-Query sollten immer mittels Hochkommata umschlossen werden. Wenn dies nicht möglich ist, unbedingt den Inhalt vorher prüfen (z.B. bei Feldernamen).
  • Auf keinen Fall die Eingaben mit einer anderen Funktion escapen. addslashes() und magic_quotes_gpc sind nicht sicher können ausgetrickst werden.

2. Value Binding mit PDO
Die sicherste Lösung ist wahrscheinlich das Value-Binding. Dabei werden die Werte erstmal durch Platzhalter vorbelegt und später von der PDO-Erweiterung aus PHP eigenständig ersetzt und vorher natürlich escaped. Hier mal ein kleines Beispiel:

$pdo = new PDO('mysql:dbname=DatenbankName;host=localhost', 'benutzer', 'ultra_sicheres_passwort');
$stmt = $pdo->prepare('SELECT * FROM blog_posts WHERE post_title LIKE ?');
$stmt->bindValue(1, $_POST['suche'], PDO::PARAM_STR);
if($stmt->execute())
{
	$result = $stmt->fetchAll();
}

Hier müssen keine Anführungszeichen oder Hochkommata verwendet werden. Die werden später von PDO automatisch – abhängig vom angegebenen Datentyp – gesetzt. Die MySQLi-Extension besitzt nebenbei bemerkt eine ähnliche Funktion.
Diese Lösung bedeutet zwar etwas mehr Code, dafür ist sie aber sauberer und bedeutend sicherer.

3. PHP IDS
Ich hatte bereits vor einiger Zeit über PHP IDS berichtet. Dabei handelt es sich um eine Bibliothek, die vor die eigentliche Anwendung geschaltet wird und vor dem Start sämtliche Benutzereingaben überwacht. Dabei werden nicht nur SQL-Injections erkannt, sondern auch XSS und andere schädliche Eingaben. Eventuelle Angriffe können so geloggt werden, oder auch direkt per Mail an einen Administrator verschickt werden.

Weitere Tipps und Best Practice:

  • Wenn ihr sicher seid, dass die Eingabe eine Zahl sein muss, reicht es auch die Variable in eine Zahl umzuwandeln (z.B. mit dem (int)-Cast). Das ist sicherer als jedes Escapen.
  • Schreibt sauberes SQL und umschließt am besten alles in Hochkommata was möglich ist. Nur so kann das Escapen wirklich funktionieren.
  • Verlasst euch nicht auf die PHP-internen Funktionen magic_quotes_runtime und magic_quotes_gpc. Diese sind seit PHP 5.3 sowieso veraltet und werden in PHP 6 mangels Sicherheit komplett entfernt.
  • Wer wirklich sicher gehen will, überprüft immer alle Eingaben vom Benutzer, ob diese überhaupt einen gültigen Wert haben. Unbedingt auch Tabellen- und Spaltennamen beachten, eben alles was vom Benutzer zur Datenbank geleitet wird.

SQL Injections Ade: PHP IDS

Nachdem mein Browsergame Yumee letzte Woche wahrscheinlich Opfer einer SQL-Injection geworden ist (fragt mich nicht wie das ging), habe ich kurzer Hand entschieden, die Library PHP IDS einzusetzen.

Kurz zu Einleitung: IDS kommen ursprünglich aus der Netzwerktechnik und überwachen dort Netzwerke auf mögliche Angriffe. Und genau das tut auch die PHP IDS. Wir lassen alle eingehenden Variablen (GET, POST und COOKIE), bevor die Anwendungen damit arbeitet, einmal überprüfen. Wenn die IDS nicht anschlägt, geht der Aufruf durch, ansonsten beenden wir Anwendung einfach.

PHP IDS hat neben seiner Hauptfunktion allerdings noch weitere Features. So lässt sich beispielsweise der HTML Pruifier anbinden, der eingehende Strings nach bösen HTML-Code durchsucht (z.B. <script> oder onclick). Außerdem gibt es verschiedene Logging-Möglichkeiten + automatische E-Mail-Benachrichtigung.
Einziger Nachteil: Es geht ein wenig Performance verloren, wobei das kaum zu merken ist.