SQL-Injection

  • Die meisten Webanwendungen interagieren mit einer Datenbank und die zu speichernden Daten bestehen meistens aus Eingaben, die ein Anwender in einem Formular eingegeben hat. Aus diesen eingegebenen Daten werden dann vom Entwickler SQL-Statements generiert und an die Datenbank geschickt. Und genau hier liegt der Ansatzpunkt für eine SQL Injection.


    Ein typisches Szenario für eine SQL-Injection ist, daß syntaktisch korrekte Eingaben mittels GET oder POST an ein Skript geschickt werden, das wenig oder gar keine Validierungsfunktionen besitzt.


    Dieses Skript schickt nun den "bösen" Code an die Datenbank und der Angreifer kann zum Beipiel Datensätze auslesen, die er normalerweise nicht sehen dürfte oder im schlimmsten Fall Schreibzugriffe auf die Datenbank ausführen.


    Versetzen wir uns zunächst in die Rolle eines Angreifers. Was ist dann unser Ziel?


    Wir möchten unsere SQL Statements an die Datenbank schicken und dadurch Ergebnisse erzielen, die vom Entwickler nicht beabsichtigt waren.


    Als erstes müssen wir uns einen Angriffspunkt suchen, zum Beispiel ein Formular oder die Übergabe einer ID in der URL (http://www.servername.de/index.php?id=1). Durch Verändern der Formulardaten oder der Parameter in der URL, können wir Fehler hervorrufen, die uns Hinweise geben können, wie das SQL Statement intern aufgebaut ist.


    Hier einige Beispiel-Fehlermeldungen:


    " You have an error in your SQL syntax. Check the manual that corresponds to your MySQL server version for the right syntax to use near ''a' at line 1


    " Unknown column 'a' in 'where clause'


    Das SQL Statement sieht dann wahrscheinlich etwa so aus: "SELECT * FROM tabellenname WHERE id=".$_GET['id']


    Ersetzt man die numerische ID in der URI durch einen beliebigen Buchstaben oder ein einfaches Anführungszeichen (Single Quote), quittiert das PHP-Script dies mit einer Fehlermeldung. Das läßt darauf schließen, daß hier wahrscheinlich ein Integer, also eine Zahl erwartet, jedoch nicht erzwungen wird. Bei einer Eingabe von 0 OR 1=1 für den Parameter "id", können hier alle vorhandenen Datensätze aus der Datenbank ausgelesen werden.


    Eine andere Tabelle kann man mit dem folgenden Inhalt für den Parameter "id" auslesen: 0 UNION SELECT name FROM tabelle2


    In diesen Beispielen bewegen wir uns aber immer in der Datenbank, die mit mysql_select_db ausgewählt wurde. Eine andere Datenbank können wir mit 0 UNION SELECT db FROM mysql.db auslesen. Es kann vielleicht noch zu Fehlermeldungen kommen, wie etwa "The used SELECT statements have a different number of columns", diese Fehler können jedoch mittels geschickter Verwendung des Keywords NULL bzw. der Funktion concat korrigiert werden. Mit einigem Probieraufwand kann der Angreifer die exakte Anzahl der erwarteten Spalten herausfinden und entsprechend viele Spalten aus eine zweiten Tabelle auslesen. Werden drei Spalten im eigentlichen, nicht modifizierten Query ausgelesen, so würde ein manipuliertes Statement wie "0 UNION SELECT db,NULL,NULL FROM mysql.db" zum Erfolg führen - die Namen aller Datenbanken auf dem Server werden angezeigt.


    CONCAT() ist eine MySQL-Funktion, die es erlaubt, verschiedene Datenbankfelder miteinander zu verbinden. Für den Fall, daß in dem angegriffenen SQL-Query nur eine Spalte aus der Datenbank selektiert wird, der Angreifer aber z.B. einen Benutzernamen und ein Passwort, also zwei Spalten herausfinden will, kann er mit CONCAT() beliebig viele Spalten konkatenieren. Ein Beispiel wäre das Teilquery "0 UNION SELECT concat(User,Password) FROM mysql.user".


    Da bei vielen MySQL-Installationen die Standardkonfiguration und oft auch der User "root" verwendet wird, bzw. die Rechte für den Datenbankserver falsch eingestellt sind, funktioniert dieses Beispiel recht häufig.


    Abhilfe schafft hier, für jede Applikation einen User einrichten, der auch nur Rechte auf die verwendete Datenbank hat und auf keine andere, schon gar nicht die "mysql"-Datenbank selber.


    Ein anderes Beispiel. Eine Bank oder eine Kreditkartenfirma, liest anhand von URL Parametern oder Formulardaten Kontostände aus. Eine URL für den Kundenbereich eines Herrn Kunz könnte so aussehen: http://www.bankserver.de/kontostand.php?id=kunz. Ein gangbarer, aber sehr gefährlicher Weg, wenn man die Validierung der Daten vergißt bzw. unzureichend durchführt.


    Bei der Eingabe von "?id=kunz'+or+id='prochaska'" erhält der Angreifer nicht nur den Kontostand von Herrn Kunz, sondern auch den von Herrn Prochaska. Bei der Eingabe von http://www.bankserver.de/konto…?id=mueller_hans+or+id=id werden im schlimmsten Fall alle Kontodaten zurückgegeben.


    Aber nicht nur SELECT Statements sind angreifbar, auch jedes andere Statement.


    Als Beispiel kann hier ein Anmeldeformular für eine Mailingliste dienen. Hier wird vom Benutzer ein Benutzername und eine E-Mail Adresse verlangt, die mit folgendem Statement in eine Datenbank geschrieben wird:


    INSERT INTO users (username,passwort,email) VALUES (' " . $_POST['user'] . "','" . $passwort . "','" . $_POST['email'] . "');


    In die Variable $passwort wurde vorher ein vom Script zufällig generiertes Passwort geschrieben.


    Gibt man nun in das Feld Benutzername den "Namen" boeser_bube','password',' '), ('echter_name sowie eine beliebige E-Mail-Adresse ein, so wird das vom Script ausgeführte Script zu "INSERT INTO users (username,passwort,email) VALUES ('boeser_bube','password',' '),('echter_name','sdfssd23d','[email protected]')" modifiziert. Damit kann zusätzlich zu dem registrierten Benutzer "echter_name" sich auch der Benutzer "boeser_bube" einloggen. In diesem vereinfachten Beispiel ergibt eine SQL-Injection aus Sicht des Angreifers natürlich keinen Sinn - schließlich könnte er sich einfach auf regulärem Wege einen Account einrichten.


    Bei Communitysoftware wird jedoch häufig bei jedem Login mindestens ein INSERT auf eine Tabelle mit gerade angemeldeten Benutzern durchgeführt - hier ergäben sich unter Umständen Angriffspunkte für eine wesentlich gefährlichere SQL-Injection über zwei verschiedene Tabellen.