SQL-Injection

SQL-Injection-Lücken erkennen und beheben

Bildnachweis: (c) Heiko Stuckmann / pixelio.de

Die OWASP TOP 10 sieht Injection als größte Gefahr in Webanwendungen. Ein häufiger Sonderfall ist SQL-Injection. Damit können nicht nur Daten geklaut, manipuliert, hinzugefügt oder gelöscht werden. Es kann sogar genutzt werden um Kontrolle über den Server zu übernehmen.

Betrachten wir folgendes Beispiel. Über solchen Code sind wir sicherlicher schon alle gestolpert.

$query = "UPDATE users SET password = '" . $_GET['password'] . "' WHERE username = '" . $_GET['username'] . "'";

Würde dieses Query ausgeführt, würde funktionieren. Allerdings klafft hier ein großes Sicherheitsloch, denn wir akzeptieren einfach so, was wir per GET an Parametern bekommen, und senden es zur Datenbank. Es gibt eine Maxime, die hier sicherlich öfter genannt werden wird: Traue keinen Daten von fremden Quellen. Egal ob GET, POST, Cookie oder APIs. Wir vertrauen Daten die wir nicht kontrollieren niemals!

SQL-Injection ausprobiert

Wie können wir jetzt den Code von oben ausnutzen? Da gibt es verschiedene Möglichkeiten. Wir könnten zum Beispiel die Passwörter von allen Usern ändern lassen, in dem wir als username folgendes übergeben:

' or '1'='1

Was würde das bewirken?

UPDATE users SET password = 'Test' WHERE username = 'dennis' or '1' = '1'

Und fertig wäre unsere erste SQL-Injection.

Man könnte noch ein ” — ” am Schluss einfügen, um den Rest des SQLs zu ignorieren. So könnte man das gleiche auch mit nur einem Passwort erreichen:

UPDATE users SET password = 'Test' -- ' WHERE username = ''

Das gleiche Funktioniert natürlich auch beim Löschen von Daten. Vorstellbar wäre auch das hinzufügen eines zweiten SQL-Statements.

Ohne zu viele Details preiszugeben möchte ich noch erklären, wie ein Angreifer per SQL-Injection sogar den Server übernehmen könnte: SELECT-Ergebnisse lassen sich bekanntlich in Files schreiben. Wenn der Angreifer dafür sorgt, dass per Injection der Code für eine Reverse-Shell in ein File im Document-Root des Webservers geschrieben wird, kann diese Shell per HTTP-Request gestartet werden.

Gegenmaßnahmen:

Es wird von OWASP empfohlen Prepared Statements zu nutzen. In PHP kann das z.B. so aussehen, dass man die entsprechenden Funktionen des DB-Treibers nutzt, oder auf eine Datenbankabstraktion zurückgreift.

PDO

Mit PDO ist es sehr einfach Prepared Statements zu nutzen.

$statement = $pdo->prepare("UPDATE users SET password = ? WHERE username = ?");
$statement->execute([$_GET["password"], $_GET["username"]]);

Die Datenbank kümmert sich um den Rest, inklusive Anführungszeichen. Es ist ausgeschlossen, hier noch SQL zu injecten.

mysqli

Mit mysqli ist es etwas mehr Aufwand, aber nicht unmöglich:

$statement = mysqli_prepare("UPDATE users SET password = ? WHERE username = ?");
$statement->bind_param("ss", $_GET["password"], $_GET["username"]);
$statement->execute();

Der erste Parameter bei bind_param ist dabei ein String mit einer Auflistung der Datentypen der Werte (hier zwei Strings), danach folgen alle Werte. Das macht es etwas kompliziert mysqli mit Prepared Statements dynamisch zu nutzen.

Empfehlung

Auch wenn Sie kein ORM nutzen wollen, würde ich eine Datenbankabstraktion empfehlen. Ich nutze gerne Doctrine DBAL.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert