Die wohl am häufigsten fatal ausgenutzte Sicherheitslücke ist die SQL-Injection. Durch diese wird ein Zugriff auf die Datenbank möglich und je nach Rechten kann der Angreifer Daten auslesen, löschen oder bearbeiten.
Wie das ein Angreifer macht und wie einfach man sich davor schützen kann will ich in diesem Eintrag zeigen.
SQL-Injection
SQL-Injection ist die Ausnutzung einer Lücke einer Schnittstelle zur SQL-Datenbank. Hierbei wird kompletter SQL-Code eingeschleust. Doch was ist diese Schnittstelle und wie entsteht diese Lücke?
Entstehung der Lücke
In Websprachen wie PHP, ASP aber auch andere “CGI-Sprachen” sind Datenbankabfragen schon fast Pflicht. Eine solche Abfrage für MySQL wäre zum Beispiel:
$id = $_GET['id']; $res = mysql_query("SELECT * FROM `users` WHERE `id` = $id"); while ($row = mysql_fetch_array($res)) { echo "Username: {$row['username']}"; echo "E-Mail: {$row['mail']}"; echo "Credits: {$row['credits']}"; }
Der wichtigste Teil ist hier:
$id = $_GET['id']; $res = mysql_query("SELECT * FROM `users` WHERE `id` = $id");
Den Rest kann man vorerst getrost ignorieren. 90% der Personen die SQL-Injections ausnutzen, tun dies erschreckender Weise auch.
Hier findet eine SQL-Abfrage für alle Daten aus der Datenbank “users” statt wo die “id” der Variable “$id” gleicht.
Die Variable “$id” wird von “$_GET[‚id‘]” definiert und diese ist wiederum ein URl-Parameter.
Wenn wir davon ausgehen, dass das Script auf http://xyz.com/shop.php liegt, so können wir “$_GET[‚id‘]” durch den Aufruf von http://xyz.com/shop.php?id=1 auf “1″ setzen.
Der aufmerksame Leser wird schon das Muster erkennen.
In dieser Abfrage gibt es allerdings einen fatalen Fehler. “$id” bzw. “$_GET[‚id‘]” könnte alles Mögliche sein. Es könnte wie geplant eine Nummer sein, aber auch ein Text.
Beides ist harmlos, doch die dritte Möglichkeit ist, dass die “$id” SQL-Code beinhaltet.
Hier wird es gefährlich, da der Attackierende volle Kontrolle über die Datenbank hat und noch dazu alles ausgegeben bekommt. Das ist die Standard-SQL-Injection.
Es gibt auch Fälle in denen keine Daten ausgegeben werden, sondern einfach nur ob Daten gefunden wurden oder nicht. Hier werden die Daten dann durch geschickte Abfragen (von 90% allerdings nur kopiert) erraten werden. Diese Art nennt man Blind-SQL-Injection.
Von hier an kann der Attackierende nun Daten auslesen, manipulieren, hinzufügen oder gar die Datenbank infizieren. Wie das gemacht wird will ich hier nicht erläutern, da es hier nicht um das Ausnutzen, sondern um das Schützen davor geht.
Schutz durch Filter
Jetzt meinen viele, dass sie die Eingaben filtern müssen um sich davor schützen zu können. Doch diese Filter haben einen großen Nachteil. Entweder sie schränken die Benutzerfreundlichkeit ein oder sie sind unsicher. Ein simpler Beispielfilter wäre:
function isOK($input) { $bad_words = array("SELECT","*","FROM","UPDATE","SET","INSERT","VALUES"); foreach ($bad_words as $word) { if ($input == $word) return false; } return true; }
Diese kann einfach durch eine Kleinschreibung umgangen werden. Auch gemischte Groß- und Kleinschreibung wird von SQL unterstützt. Doch auch wenn alle möglichen Schreibweisen gefiltert werden (Stichwort: strupper/strtoupper), so ist es nicht mehr möglich diese zu verwenden. So könnte ich zum Beispiel dieses Tutorial nicht posten.
Ein sehr verbreiteter Schutz ist es die Eingaben durch mysql_real_escape_string() zu filtern. Diese Funktion bereitet einen String so auf, dass man diesen in einer SQL-Abfrage verwenden kann. Angeblich ist auch diese Methode umgehbar (wahrscheinlich mit diversen HEX-Tricks).
Doch all diese Sachen beherbergen bloß einen unnötig großen Aufwand und bieten trotzdem keine 100%ige Sicherheit.
Anders ist das bei den Prepared Data Objects von PHP.
Schutz durch PDO
PDO bezeichnet eine Klasse in PHP und steht für Prepared Data Objects. Diese bietet Datenbankzugriff auf Treiberebene. Das heißt für uns, dass Daten wie “$id” nicht gefiltert werden müssen, da sie direkt auf Treiberebene verarbeitet werden und somit so wie sie sind, als Daten, übergeben werden.
Das ist nicht nur viel einfacher, sondern bietet auch 100%igen Schutz gegen SQL-Injections.
Wer sich diese noch nicht angeeignet hat, kann das u.A. hier nachholen.
Zum Abschluss gibt es noch eine meiner Lieblingsfunktionen für euch:
try { $db_conn = new PDO("mysql:host=$db_host;dbname=$db_name",$db_user,$db_pass); } catch(PDOException $e) { echo "Error connecting:”.$e->getMessage().”"; } function db_query($sql) { $args = func_get_args(); $dbc = $GLOBALS['db_conn']; $stmt = $dbc->prepare($sql); for ($i = 1; $i < func_num_args(); $i++) $stmt->bindParam(":$i", $args[$i]); try { $stmt->execute(); } catch(PDOException $e) { echo "Database error:”.$e->getMessage().”"; } return $stmt; }
Aufgerufen wird sie in Form von:
db_query("SELECT * FROM `table` WHERE `field1` = :1 AND `field2` = :2 AND `field3` = :3", $field1, $field2, $field3);
Dies ist also eine Funktion wie printf(), nur dass statt %s und co “:1″ für den 2. Parameter (hier “$field1″) steht. Dadurch ist die Reihenfolge auch nicht wichtig.
Schlusswort
Wie man sieht bieten PDOs nicht nur Sicherheit, sondern auch noch viele andere Vorteile.
Und doch nutzen viele PHP-Entwickler weiterhin Funktionen wie „mysql_query()“.