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.