Wikipedia · einfach zusammengefasst · Stand
SQL-Injection
SQL-Injection (dt. SQL-Einschleusung) ist das Ausnutzen einer Sicherheitslücke in Zusammenhang mit SQL-Datenbanken. Die Sicherheitslücke entsteht durch …
Inhalt5 Abschnitte
Grundprinzip und Voraussetzungen
SQL-Injection, deutsch SQL-Einschleusung, bezeichnet das Ausnutzen einer Sicherheitslücke im Zusammenhang mit SQL-Datenbanken. Die Ursache ist ein Programmierfehler in einem Anwendungsprogramm, das Eingaben an eine Datenbank weitergibt. Statt Eingaben ausschließlich als Daten zu behandeln, verwendet das Programm sie unmittelbar zum Erzeugen einer Datenbankabfrage. Dadurch kann ein Angreifer Teile dieser Abfrage steuern.
Für einen erfolgreichen Angriff sind vier Voraussetzungen erforderlich: eine angegriffene SQL-Datenbank, zum Beispiel MySQL, Microsoft SQL Server oder Db2; ein Anwendungsprogramm mit Eingabemöglichkeiten, etwa eine Loginmaske, Produktsuche oder ein Kontaktformular; die Weitergabe dieser Eingaben an die Datenbank; sowie der beschriebene Programmierfehler.
In SQL haben bestimmte Zeichen eine besondere Bedeutung. Dazu gehören der Apostroph und in einigen Datenbanken das Anführungszeichen zum Umschließen von Text, der Backslash zum Entschärfen des folgenden Zeichens, das Semikolon zum Trennen von Befehlen und der doppelte Bindestrich zum Auskommentieren des Rests einer Zeile. Auch dokumentenorientierte NoSQL-Datenbanken können betroffen sein; dort spielen insbesondere geschweifte und eckige Klammern eine Rolle, die JSON-Objekte beziehungsweise Arrays umschließen.
Wie eine Abfrage manipuliert wird
Eine normale Abfrage nach Artikeln einer Kategorie lautet beispielsweise:
SELECT id, titel, artikeltext FROM artikel WHERE kategorie = 'gesuchte Kategorie';
Großgeschriebene Wörter sind SQL-Schlüsselwörter; kleingeschriebene Wörter bezeichnen Tabellen, Zeilen oder Spalten. Text in einfachen Anführungszeichen ist ein fester Text beziehungsweise ein Platzhalter. Wird die Kategorie „Rock ’n’ Roll“ naiv eingesetzt, entsteht:
SELECT id, titel, artikeltext FROM artikel WHERE kategorie = 'Rock 'n' Roll';
Der Apostroph hat dann zugleich die Bedeutung eines Begrenzers und eines wörtlichen Zeichens. Die Datenbank kann die Abfrage nicht sinnvoll interpretieren und meldet einen Fehler.
Bei einer SQL-Injection wählt der Angreifer die Eingabe so, dass die Abfrage weiterhin gültig ist, aber eine andere Bedeutung erhält. Die Eingabe „asdfasdf' OR id < 100 OR kategorie = 'sdfgsdfg“ erzeugt:
SELECT id, titel, artikeltext FROM artikel WHERE kategorie = 'asdfasdf' OR id < 100 OR kategorie = 'sdfgsdfg';
Mindestens eine der drei Bedingungen muss wahr sein. Die erste und dritte Bedingung halten die Abfrage formal wohlgeformt; relevant bleibt id < 100. Statt nur eine Kategorie zu durchsuchen, liefert die Abfrage damit möglicherweise alle Artikel mit einer ID kleiner als 100. Der Kernfehler besteht darin, den Suchbegriff 1:1 in die Abfrage zu übernehmen.
Mögliche Folgen eines Angriffs
Je nach Datenbank, Anwendung und Berechtigungen kann SQL-Injection unterschiedliche Auswirkungen haben:
-
Bei der Veränderung von Daten kann ein zusätzlicher Befehl eingeschleust werden. Aus dem erwarteten Aufruf „http://webserver/cgi-bin/find.cgi?ID=42“ mit „SELECT author, subject, text FROM artikel WHERE ID=42;“ kann durch „ID=42;UPDATE+USER+SET+TYPE="admin"+WHERE+ID=23“ eine Abfrage werden, die den Benutzer mit ID 23 zum Administrator macht.
-
Ein weiterer Befehl kann auch den Datenbankserver beeinflussen. Im Beispiel mit Microsoft SQL Server wird an eine Suche nach „sql“ „;GO EXEC cmdshell('shutdown /s') --“ angehängt. Die zwei Bindestriche kommentieren das übrig gebliebene Hochkomma aus. So kann ein Windows-Prozess ausgeführt werden, sofern der Datenbankprozess die nötigen Administratorrechte besitzt.
-
Mit der UNION-Klausel lassen sich mehrere SELECT-Abfragen zu einer Ergebnismenge verbinden. Alle verbundenen SELECTs müssen dieselbe Anzahl von Spalten besitzen. Im Beispiel wird deshalb „x“ verwendet: „SELECT author, subject, text FROM artikel WHERE ID=42 UNION SELECT login, password, 'x' FROM user;“. Die Spaltenanzahl kann etwa mit „ID=42 order by x--“ ermittelt werden: Lädt die Seite bei x = 8 normal, bei x = 9 aber mit Fehlermeldung oder anderer Seite, beträgt die Spaltenanzahl 8.
-
Bei fehlerhafter Konfiguration können Systemtabellen, Systemvariablen und weitere Datenbanken ausgelesen werden. Bei MySQL liegen solche Informationen in „information_schema“. Darüber können unter anderem „table_schema“, „table_name“ und „column_name“ aus „information_schema.columns“ ermittelt werden. Manche Datenbanksysteme erlauben außerdem das Auslesen beliebiger Dateien, auf die der Datenbankprozess zugreifen kann.
-
Auch NoSQL-Datenbanken sind gefährdet. In MongoDB kann die Eingabe „ID['$ne']=“ zu „db.users.find({ userID: { '$ne': undefined } });“ führen. „$ne“ bedeutet „ungleich“; dadurch können alle Benutzer zurückgegeben werden, deren Benutzername nicht leer ist.
-
Unterstützt der Server „SELECT … INTO OUTFILE“ oder „SELECT … INTO DUMPFILE“, können Dateien auf dem Dateisystem abgelegt werden. Ist etwa das Bibliotheksverzeichnis beschreibbar, kann theoretisch beliebiger Code ausgeführt werden.
Mögliche Schäden reichen daher vom Ausspähen nicht bestimmter Daten über das Ändern oder Löschen von Daten bis zur Ausführung von Betriebssystembefehlen und zur Übernahme des Datenbankservers.
Spezielle Angriffsformen und Serverrisiken
Zeitbasierte Angriffe nutzen Benchmark-Funktionen des Datenbankservers, um Informationen über die Datenbankstruktur aus Antwortzeiten abzuleiten. In Verbindung mit dem IF-Konstrukt kann beispielsweise geprüft werden, ob der aktuelle Benutzer root ist:
SELECT IF( USER() LIKE 'root@%', BENCHMARK(100000,SHA1('test')), 'false');
Auf einem MySQL-Datenbankserver benötigt dieses Beispiel mehrere Sekunden, falls der gegenwärtige User root ist. Verwandt sind boolesche Angriffe, bei denen durch Ja-nein-Abfragen schrittweise Datenbankinhalte ermittelt werden. Das Kopieren großer Datenmengen dauert bei zeitbasierten Verfahren allerdings wesentlich länger.
Bei einer blinden SQL-Injection liefert der Server keine beschreibende Fehlermeldung, aus der der Erfolg der Abfrage hervorgeht. Ein versierter Angreifer kann den Erfolg trotzdem anhand kleiner Unterschiede, etwa verschiedener Fehlermeldungen oder charakteristischer Antwortzeiten, feststellen.
Bestimmte Datenbankserver boten zusätzliche Angriffsmöglichkeiten. Beim Microsoft SQL Server waren bis zur Version 2000 Stored Procedures wie „Xp_cmdshell“ automatisch vorhanden. Sie konnten verwendet werden, um Befehle mit den Rechten des SQL-Serverprogramms auszuführen oder eine Shell zu starten. In neueren Versionen ist diese Funktion standardmäßig deaktiviert.
Auch die Datenbanksoftware selbst kann Schwachstellen enthalten. Die PHP-Funktion „mysql_real_escape_string()“ ermöglichte im MySQL Server einen auf Unicode-Zeichen beruhenden SQL-Injection-Angriff, obwohl Eingaben korrekt maskiert waren. Dieser Fehler wurde in Version 5.0.22 am 24. Mai 2006 behoben.
Schutz durch Trennung von Code und Daten
Benutzereingaben dürfen nicht unkontrolliert in SQL-Anweisungen eingebaut werden. Erwartete Werte können zusätzlich geprüft werden; deutsche Postleitzahlen bestehen beispielsweise nur aus Ziffern. Die wichtigste Maßnahme sind jedoch gebundene Parameter in Prepared Statements. Dabei wird ein bereits kompilierter SQL-Befehl von den Eingabedaten getrennt. Die Daten werden als Parameter übergeben und deshalb nicht als SQL-Code interpretiert. Prepared Statements können außerdem die Performance verbessern. Stored Procedures bieten dagegen keinen generellen Schutz, besonders wenn ihr SQL-Code nicht bekannt ist.
Unsicher ist beispielsweise:
sql = "SELECT spalte1 FROM tabelle WHERE spalte2 = '" + eingabedaten + "';" query = db.query(sql)
Sicherer ist die Trennung durch einen Platzhalter:
sql = "SELECT spalte1 FROM tabelle WHERE spalte2 = :eingabedaten;" query = db.query(sql, eingabedaten=eingabedaten)
Die konkrete Umsetzung hängt von der Programmiersprache ab. Python-Programme mit SQLAlchemy sollten rohe SQL-Befehle vermeiden, wenn Form- oder URL-Anfragen sie beeinflussen können, und stattdessen Methoden wie „session.query(Order).get(order_id)“ oder „session.query(Order).filter(Order.status == 'active')“ verwenden. In Visual Basic (ADOdb) werden „ADODB.Command“-Objekte und Parameter verwendet; in Delphi wird etwa „:PI“ mit „ParamByName('PI').AsInteger“ gebunden. C# mit ADO.NET nutzt Platzhalter wie „@spalte2Wert“ und „cmd.Parameters.AddWithValue“. Java verwendet „PreparedStatement“, den Platzhalter „?“ und beispielsweise „pstmt.setString(1, spalte2Wert)“.
In PHP stehen PHP Data Objects und „prepare“ beziehungsweise „bindParam“ zur Verfügung. „magic_quotes_gpc“ war bis PHP 5.3 eine Konfigurationsoption, ist jedoch keine verlässliche Schutzmaßnahme. „addslashes()“ bietet keine ausreichende Sicherheit gegenüber „mysql_real_escape_string()“. Nach PHP 5.3 wurde „mysql_real_escape_string()“ durch MySQLi ersetzt; ab PHP 7.0 ist die alte Funktion nicht mehr verfügbar, und ab PHP 7.x heißt die entsprechende Funktion „mysqli_real_escape_string()“. Grundsätzlich sind Prepared Statements vorzuziehen.
Perls DBI unterstützt Platzhalter, „prepare“ und „execute“ sowie datenbankspezifisches Maskieren durch „quote“. Im „taint mode“, der mit „-T“ aktiviert wird, gelten Benutzereingaben zunächst als unsicher und müssen ausdrücklich validiert werden. ColdFusion bietet das Tag „<cfqueryparam>“, und bei MS-SQL schützen parametrisierte Kommandos wie „SELECT COUNT(*) FROM Users WHERE UserName=? AND UserPasswordHash=?“ vor SQL-Injection.
Zusätzlich sollten Datenbankkonten einer Webanwendung nur die tatsächlich benötigten Privilegien besitzen. Eine Web Application Firewall (WAF) kann Angriffe teilweise verhindern und die Sicherheit ergänzen, insbesondere wenn der Betreiber die Anwendung nicht kontrolliert.