SQL-Injection
SQL Injektion (engl.: SQL-Injection) bezeichnet das Ausnutzen einer Sicherheitslücke in Zusammenhang mit SQL-Datenbanken. Besagte Sicherheitslücke entsteht bei mangelnder Maskierung beziehungsweise Überprüfung von Funktionszeichen. Der Angreifer versucht über die Anwendung, die den Zugriff auf die Datenbank bereitstellt, eigene Datenbankbefehle einzuschleusen. Sein Ziel ist es hierbei, Kontrolle über die Datenbank respektive den Server zu erhalten.
Auftreten
SQL-Injection-Bugs treten auf, wenn eine Applikation SQL-Abfragen an den Server weiterreicht, ohne die vom Benutzer eingegebenen Parameter gesondert zu prüfen und etwaig enthaltene Funktionszeichen zu maskieren, und ihren so ihre Sonderfunktion zu nehmen. Funktionszeichen in SQL sind zum Beispiel der umgekehrte Schrägstrich, der Apostroph oder das Semikolon. Diese Zeichen können durch Voranstellen des Maskierungszeichens, einem umgekehrten Schrägstrich, als Text gekennzeichnet werden. Dieser Vorgang wird auch Escapen genannt. Ein Beispiel: Die vom Benutzer eingegebene Zeichenfolge "Mit '\n' wird ein Zeilenumbruch erzeugt" wird korrekt maskiert als "Mit \'\\n\' wird ein Zeilenumbruch erzeugt" an die Datenbank übergeben. Außerdem muss sichergestellt werden, dass Datentypen eingehalten werden; so dürfen zum Beispiel Zahlen nur aus Ziffern und dem Dezimaltrennzeichen bestehen.
Oft zu finden sind SQL Injection-Lücken in CGI-Scripten und auch in Programmen, die Daten wie Webseiteninhalte oder E-Mails in SQL-Datenbanken eintragen. Nimmt ein solches Programm die Maskierung nicht korrekt vor, kann ein Angreifer durch den gezielten Einsatz von Funktionszeichen weitere SQL-Anforderungen einschleusen oder die Abfragen so manipulieren, dass zusätzliche Daten ausgegeben werden. In einigen Fällen besteht auch die Möglichkeit, Zugriff auf eine Shell zu erhalten, was im Regelfall die Möglichkeit zur Kompromittierung des gesamten Servers bedeutet.
Stored Procedures sind in diesem Zusammenhang sicherer. Dabei werden die Benutzereingaben in einem Programm an die Stored Procedures übergeben. Erst dort wird die SQL-Abfrage erzeugt und ausgeführt, wodurch die Injektion weiterer SQL-Befehle in Benutzereingaben verhindert wird.
Vorgang
Veränderung von Daten
Auf einem Webserver findet sich das Script find.cgi zum Anzeigen von Artikeln. Das Script akzeptiert den Parameter ID, welcher später Bestandteil des SQL-Befehls wird. Folgende Tabelle soll dies illustrieren:
Keine SQL-Injektion | |
---|---|
Aufruf | http://webserver/cgi-bin/find.cgi?ID=42 |
Erzeugtes SQL | SELECT author, subjekt, text FROM artikel WHERE artikel ID=42 |
SQL-Injektion | |
Aufruf | http://webserver/cgi-bin/find.cgi?ID=42;UPDATE%20USER%20SET%20TYPE="admin"%20WHERE%20ID=23 |
Erzeugtes SQL | SELECT author, subjekt, text FROM artikel WHERE ID=42; UPDATE USER SET TYPE="admin" WHERE ID=23 |
Wie man erkennen kann, wird dem Programm ein zweiter SQL-Befehl untergeschoben, der die Benutzertabelle modifiziert.
Datenbank Server verändern
Auf einem Webserver findet sich das Script search.aspx zum Suchen nach Webseiten. Das Script akzeptiert den Parameter keyword, welcher später Bestandteil des SQL-Befehls wird. Folgende Tabelle soll dies illustrieren:
Keine SQL-Injektion | |
---|---|
Aufruf | http://webserver/search.aspx?keyword=sql |
Erzeugtes SQL | SELECT url, title FROM myindex WHERE keyword LIKE '%sql' |
SQL-Injektion | |
Aufruf | http://webserver/search.aspx?keyword=sql' GO EXEC cmdshell('format C') -- |
Erzeugtes SQL | SELECT url, title FROM myindex WHERE keyword LIKE '%sql' GO EXEC cmdshell('format C') --' |
Hier wird der eigentlichen Abfrage ein weiterer Befehl angehängt. Die zwei Bindestriche (--) kommentieren das Hochkomma als Überbleibsel der eigentlichen Anfrage aus. Der Befehl ermöglicht das Formatieren der Festplatte, aber auch Downloads oder ähnliches lassen sich dadurch erzeugen (am Beispiel Microsoft SQL Server).
Ausspähen von Daten
Auf manchen SQL Implementationen ist die UNION-Klausel verfügbar. Diese erlaubt es, mehrere SELECTs gleichzeitig abzusetzen, die dann eine gemeinsame Ergebnismenge zurückliefern. Durch eine geschickt untergeschobene UNION-Klausel kann man beliebige Tabellen und Systemvariablen auslesen.
Keine SQL-Injektion | |
---|---|
Aufruf | http://webserver/cgi-bin/find.cgi?ID=42 |
Erzeugtes SQL | SELECT text FROM WHERE artikel ID=42 |
SQL-Injektion | |
Aufruf | http://webserver/cgi-bin/finduser.cgi?ID=42%20UNION%20SELECT%20login,%20password,%201%20FROM%20user |
Erzeugtes SQL | SELECT author, subjekt, text FROM artikel WHERE ID=42 UNION SELECT login, password, 1 FROM user |
Die 1 beim UNION SELECT ist nötig, weil alle mit UNION verknüpften SELECTs die gleiche Anzahl von Spalten haben müssen. Der Angreifer muss also wissen wieviele Spalten die ursprüngliche Abfrage hat.
Ist der Datenbankserver fehlerhaft konfiguriert und hat beispielsweise der Benutzer, der aktuell mit der Datenbank verbunden ist und über den die SQL-Injection abgesetzt werden soll, Zugriff auf Systemdatenbanken, so kann der Angreifer über eine einfache SQL-Syntax wie Systemdatenbank.SystemtabelleMitTabellenAuflistung auf die Systemtabellen zugreifen und sämtliche Tabellen einer bestimmten Datenbank auslesen, wodurch er wichtige Informationen erhält um weitere Angriffe durchzuführen und tiefer in das System einzudringen.
Einschleusen von beliebigem Code
Eine weniger bekannte Variante stellt auch gleichzeitig die potentiell gefährlichste dar. Wenn der Datenbankserver die Kommandos SELECT ... INTO OUTFILE beziehungsweise SELECT ... INTO DUMPFILE unterstützt, können diese Kommandos dazu benutzt werden Dateien auf dem Dateisystem des Datenbankserver abzulegen. Theoretisch ist es dadurch möglich, falls das Bibliotheksverzeichnis des Betriebssystems oder des Datenbankservers für denselben beschreibbar ist (wenn dieser zum Beispiel als root läuft), einen beliebigen Code auf dem System auszuführen.
Zeitbasierte Angriffe
Wenn der Datenbankserver Benchmark-Funktionen unterstützt kann der Angreifer diese dazu nutzen, um Informationen über die Datenbankstruktur in Erfahrung zu bringen. In Verbindung mit dem if Konstrukt sind der Kreativität des Angreifers kaum Grenzen gesetzt.
Das folgende Beispiel benötigt auf einem MySQL-Datenbankserver mehrere Sekunden, falls der gegenwärtige User root ist:
SELECT if( user() like 'root@%', benchmark(100000,sha1('test')), 'false');
Erlangung von Adminstratorrechten
Bei bestimmten Datenbankservern, wie dem Microsoft SQL Server werden Stored Pocedures mitgeliefert, die unter anderem dazu missbraucht werden können, um einen neuen Benutzer auf dem angegriffenen System anzulegen.
Diese Möglichkeit kann dazu benutzt werden, um zum Beispiel eine Shell auf dem angegriffenen Rechner zu starten.
Gegenmaßnahmen
Es ist nicht schwer bestehende Programme so umzubauen, dass SQL-Injektionen nicht mehr möglich sind. Das hauptsächliche Problem der meisten Programmierer ist fehlendes Wissen über diese Art von Angriffen. Nachfolgend einige Beispiele um die Angriffe abzuwehren.
Außerdem sollten nicht benötigte Stored Procedures aus dem Datenbanksystem entfernt werden. Beim Microsoft SQL Server sollte auch geprüft werden, ob der Einsatz unter einem Account mit eingeschränkten Zugriffsrechten möglich ist, was in der Regel der Fall sein dürfte.
Microsoft .NET Framework
Im .NET Framework gibt es einfache Objekte, mit denen man solche Probleme umgeht.
Anstatt
SqlCommand cmd = new SqlCommand("SELECT spalte1 FROM tabelle WHERE spalte2 = '" + spalte2Wert + "';");
sollte folgendes verwendet werden
string spalte2Wert = "Mein Wert"; SqlCommand cmd = new SqlCommand("SELECT spalte1 FROM tabelle WHERE spalte2 = @spalte2Wert;"); cmd.Parameters.Add("@spalte2Wert", spalte2Wert);
Java
SQL-Injection kann leicht durch bereits vorhandene Funktion verhindert werden. In Java wird zu diesem Zweck die PreparedStatement-Klasse verwendet.
Anstatt
Statement stmt = con.createStatement(); ResultSet rset = stmt.executeQuery("SELECT spalte1 FROM tabelle WHERE spalte2 = '" + spalte2Wert + "';");
sollte folgendes verwendet werden
PreparedStatement pstmt = con.prepareStatement("SELECT spalte1 FROM tabelle WHERE spalte2 = ?"); pstmt.setString(1, spalte2Wert); ResultSet rset = pstmt.executeQuery();
Der Mehraufwand an Schreibarbeit durch die Verwendung der PreparedStatement-Klasse zahlt sich jedoch durch einen positiven Performancegewinn aus, da durch die Angaben bereits im Voraus eine Optimierung durchgeführt werden kann.
PHP
In PHP wird zu diesem Zweck die Funktion mysql_real_escape_string() verwendet, die jedoch lediglich für eine mySQL-Verbindung benutzbar ist. Wird eine andere Datenbank benutzt, so steht diese Funktion nicht zur Verfügung, jedoch hält PHP für fast jede Datenbank eine solche Escape-Funktion bereit. Die PHP-Oracle-Funktionen beispielweise besitzen keine Escapefunktion, hingegen können sie PreparedStatements verwenden, was bei der beliebten mySQL-Datenbank erst mit den Funktionen von mySQLi möglich geworden ist.
Anstatt
$abfrage = "SELECT spalte1 FROM tabelle WHERE spalte2 = '".$_POST['spalte2Wert']."'"; $query = mysql_query($abfrage) or die("Datenbankabfrage ist fehlgeschlagen!");
sollte folgendes verwendet werden
$abfrage = "SELECT spalte1 FROM tabelle WHERE spalte2 = '".mysql_real_escape_string($_POST['spalte2Wert'])."'"; $query = mysql_query($abfrage) or die("Datenbankabfrage ist fehlgeschlagen!");
Falls man eine Einflussmöglichkeit auf die Konfigurationsdatei php.ini hat, kann man auch die Option
"magic_quotes_gpc = off" auf "magic_quotes_gpc = on" stellen.
Dies ist jedoch an sich nicht empfehlenswert, da manche, nicht selber programmierte Scripte eigenständig über Funktionen wie addslashes() oder das bereits weiter oben genannte mysql_real_escape_string() escapen , d.h. dass bereits allen relevanten Zeichen in den Benutzereingaben durch magic_quotes ein Backslash vorangestellt wurde und nun durch die Escape-Funktion erneut ein Backslash vorangestellt wird. Somit verfälscht man die Benutzereingaben und erhält anstatt einem einfachen Anführungszeichen ein Anführungszeichen mit vorangestelltem Backslash ( \" ) zurück.
Die folgende Funktion erkennt selber ob die "magic_quotes" aktiviert sind und verfährt dementsprechend:
function quotesqlvar($value) { // Stripslashes if quoted if (get_magic_quotes_gpc()) { $value = stripslashes($value); } // Quote if not integer if (!is_numeric($value)) { $value = "'" . mysql_real_escape_string($value) . "'"; } return $value; }
Ein "Vorteil" von PHP ist weiterhin, dass in einem Aufruf von mysql_query() nicht wie in anderen Programmiersprachen 2 SQL-Statements (zum Beispiel ein DELETE-Statement) ausgeführt werden können, sondern jeweils ein seperater Aufruf von mysql_query nötig ist.
Perl
Das datenbankunabhängige Datenbankmodul DBI unterstützt eine ähnliche "prepare" Syntax die auch im Java Beispiel zu sehen ist.
$statementhandle = $databasehandle->prepare("SELECT spalte1 FROM tabelle WHERE spalte2 = ?"); $returnvalue = $statementhandle->execute( $spalte2Wert );
MS-SQL
Über parametrisiertes Kommando kann die Datenbank vor SQL-Code-Injection geschützt werden:
SELECT COUNT(*) FROM Users WHERE UserName=? AND UserPassword=?