Die große Gefahr durch SQL-Injection-Lücken - und wie man sie vermeidet.

Die sog. SQL-Injection-Lücken sind eine wirklich gefährliche Angelegenheit.
Heise Security beschreibt wunderbar in seinem Know-how Bereich das Thema SQL-Injection-Lücken.
Sind jemandem solche Lücken in einem System bekannt oder werden sie durch ausprobieren gefunden, droht die Gefahr, dass der Angreifer beliebig MySQL-Code ausführen kann und somit persönliche Daten auslesen, verändern oder löschen könnte.

Daher ist eine saubere Programmierung absolut wichtig.
Nicht saubere Programmierung bedeutet in diesem Zusammenhang vorallem, dass die Parameter die z.b. für WHERE-Angaben übergeben werden nicht gefiltert werden.

Ungefiltert sieht das so aus:

Mit dem SQL-Befehl

SELECT * FROM kunde WHERE card = 'visa'

liefert eine Datenbank alle Datensätze der Tabelle kunde zurück, die in der Spalte card den Wert visa abgelegt haben. Ersetzt man die konstante Zeichenkette visa durch eine Variable $card, so sind in Verbindung mit einer Benutzereingabe verschiedene Zeichenketten möglich:

SELECT * FROM kunde WHERE card = '$card'

Solange in der Variablen Werte wie visa, amex oder master stehen, reagiert die Datenbank wie erwartet. Gibt ein böswilliger Benutzer jedoch die Zeichenkette ‘;DROP TABLE KUNDE– ein, schickt die Applikation folgendes an die Datenbank:

SELECT * FROM kunde WHERE card = '';DROP TABLE KUNDE--'

Da das Semikolon ein Trennzeichen darstellt, sieht die Datenbank zwei Befehle:

SELECT * FROM kunde WHERE card = ''

zeigt alle Datensätze deren Spalte card leer ist. Anschließend führt die Datenbank den zweiten Befehl aus, der die Tabelle kunde komplett löscht.

DROP TABLE KUNDE--'

Die zwei Bindestriche kennzeichnen den Anfang eines Kommentars, weshalb auch das letzte Hochkomma (Quote) ignoriert wird, statt einen Fehler hervorzurufen. In diesem Beispiel kann ein Angreifer ohne vorherige Authentifizierung ganze Tabellen von der Festplatten wischen. Allerdings muss die Web-Applikation die erforderlichen Zugriffsrechte besitzt. Beim Anlegen des entsprechenden Datenbanknutzers muss der Datenbank-Administrator das Löschen mittels DROP erlauben.

Das grundlegende Problem bei SQL-Injection ist die fehlende Filterung der Eingaben auf mögliche Quotes. Viele Applikationen setzen einen SQL-Befehl aus Stringelementen zusammen, die von Hochkommas eingerahmt werden müssen. Unter Java sieht das dann so aus:

String sql = new String("SELECT * FROM kunden WHERE
card= '" + request.getParameter("cardname")"'")

Java expandiert die Eingabe cardname und fügt sie dem feststehenden String hinzu. Mögliche Quotes in cardname bleiben erhalten, erscheinen nun aber in einem anderem Kontext. Der ursprünglichen Befehl kann um weitere Befehle ergänzt werden.

Der Angriff

Durch das Einschleusen eigener Befehle ist es möglich, auf beliebige Tabellen und Daten zuzugreifen und deren Inhalte zu manipulieren, zum Beispiel Kreditkartennummern, die ein Webshop zu Abrechnungszwecken gespeichert hat. Dazu muss ein Angreifer aber die Struktur der Datenbank kennen. Um etwa aus einer Tabelle zu lesen, ist es notwendig, den Tabellennamen anzugeben. Ohne Einblick in den Source-Code muss er raten — oder die Datenbank dazu bewegen, Informationen preiszugeben.

Je nach Plattform und Umgebung, zeigen Web-Applikationen in bestimmten Situationen dem Anwender Fehlermeldungen an. Beispielsweise geben Microsofts Active Server Pages über misslungene Zugriffe auf den MS-SQL-Server Auskunft.

Übergibt man im Web-Frontend als Usernamen

' having 1=1--

an, so erzeugt die Programmzeile

var Query = "SELECT * FROM users WHERE username = '" + username + "'
and password =" ' " + password + "'";

eine Fehlermeldung, da das SQL-Statement HAVING an dieser Stelle eigentlich nichts zu suchen hat. Sinngemäß lautet die Meldung: “Die Spalte users.username ist ungültig, da sie in keiner Funktion enthalten ist und kein GROUP-BY-Argument angegeben ist.”

Ein Angreifer weiß nun, dass es in der Tabelle users die Spalte username gibt und kann weitere Versuche starten:

' group by users.username having 1=1--

produziert die Ausgabe: “Die Spalte users.password ist ungültig, da sie in keiner Funktion enthalten ist und kein GROUP-BY-Argument angegeben ist.” Zusätzlich kennt er nun den Namen eines weiteren Feldes. Neben HAVING sind auch andere SQL-Befehle möglich, um Fehlermeldungen zu produzieren. Den Variablentyp erfährt der Angreifer durch die Eingabe

' union select sum(username) from users--

Die Applikation meldet einen Fehler, wenn username vom Typ varchar ist: Die Funktion sum() erlaubt nämlich keine varchars als Argument.

Mit vielen provozierten Meldungen kann man sich nach und nach ein Bild vom Aufbau der Datenbank machen. Anschließend ist es dann relativ einfach, eigene Datensätze zu schreiben, zu ändern, zu lesen oder zu sogar zu löschen.

Der Meister aller SQL-Krieger kann manchmal noch mehr anrichten…

Unterstützt die Datenbank besondere Features, so ist es sogar möglich mit dem darunterliegenden Betriebssystem zu kommunizieren. Für einen MS-SQL-Server auf Windows 2000 und VBScript findet man häufig solche Anweisungen:

var Query = "SELECT * FROM myKunde WHERE card = '"
& request.form("eingabe") & "'";
Set myDatensatz = myConnection.execute(Query);

Mit dem Befehl lassen sich allen Datensätze anzeigen, in denen die Variable eingabe identisch mit dem Inhalt des Feldes card. Leider hat auch hier der Programmierer darauf vertraut, dass der Bediener keine bösen Absichten hat. Gibt dieser nämlich

' exec master..xp_cmdshell 'net user foo bar /ADD'--

ein, so macht das Skript daraus den Befehl:

SELECT * FROM myKunde WHERE card = ''
exec master..xp_cmdshell 'net user foo bar /ADD'--'

Der MS-SQL-Server liest den letzten Teil des Kommandos als sogenannte Extended Stored Procedures (ESP), die er auf dem Server ausführt[1]. Die Funktion xp_cmdshell übergibt den sich anschließenden String als Befehl an die DOS-Kommandozeile. Das Beispiel fügt damit dem System, beispielsweise Windows 2000, den User foo mit dem Password bar hinzu. ESPs sind im Prinzip dynamische Bibliotheken, die zur Laufzeit nachgeladen und ausgeführt werden. Neben MS-SQL unterstützen auch andere Datenbankhersteller Extended Stored Procedures, die diverse Funktionen bieten, beispielsweise Registry-Keys lesen und schreiben oder Dienste starten und stoppen[9].

Und so sollte der Code korrekter weise aussehen…

Um SQL-Injection zu vermeiden, ist es notwendig, eingegebene Zeichenketten sorgfältig zu überprüfen und zu filtern, bevor die Applikation sie als Argument in einen SQL-Befehl expandiert und an die Datenbank sendet. Die Strategie des Filters sollte restriktiv sein. Dazu gibt es zwei Ansätze, die je nach Einsatzumgebung sinnvoll sind. Ist die zu erwartende Menge möglicher Benutzereingaben vorhersagbar, sollen nur diejenigen erlaubt sein, die in einer definierten Liste aufgeführt sind. Der Inhalt eines Feldes in das Anwender beispielsweise Automarken eingeben können, lässt sich mit einer Liste aller bekannten Marken vergleichen. Die Applikation verwirft die Eingabe, wenn sie keinem Eintrag in der Liste entspricht. Ähnlich den Filteransätzen bei Firewalls wäre dies eine “deny all, allow some”-Strategie.

Schwieriger wird das Filtern bei nicht vorhersagbaren Eingaben wie Straßennamen, Nachnamen und Telefonnummern. Hier funktioniert nur der “allow all, deny some”-Ansatz zufriedenstellend. Die Applikation darf nur Eingaben an die Datenbank weitergeben, die definierte Elemente nicht enthält: Das Quote-Zeichen und das Semikolon sind verboten. Auch die hintereinander gestellten Bindestriche (”–”) dürfen nicht in der Zeichenkette zu finden sein. Andernfalls verwirft das Programm die Eingabe.

Zusätzlich sollte der Filter nach SQL-Befehlen in der Zeichenkette suchen. Trifft er dabei auf solch einen String, verwirft er ebenfalls die Anfrage. Allerdings ist dieses Vorgehen nicht immer pauschal richtig. Einige Content-Management-System speichern Texte in der Datenbank. Der vorliegende Text würde bei Berücksichtigung obiger Hinweise nie den Weg in solch ein System finden. Hier muss der Programmierer verschiedene Methoden kombinieren.

Sehr empfehlenswert ist der Einsatz von Prepared SQL-Statements beziehungsweise Stored Procedures. Dazu definiert man im Programm Templates der gewünschten SQL-Befehle und sendet sie an die Datenbank:

SELECT * FROM users WHERE card = ?

Diese speichert die Befehle und fügt später nur noch die zur Laufzeit übergebenen Parameter hinzu. Ein nachträgliches Einschleusen von zusätzlichen SQL-Befehlen ist dann nicht mehr möglich.

Schützenhilfe bei der Erstellung und dem Betrieb von Datenbank-Applikationen erhalten Programmierer von diversen Tools. Diese helfen schon in der Entwicklungsphase kritische Funktionen und Variablen zu erkennen und zu verbessern[8]. Spezielle Schwachstellen-Scanner überprüfen laufende Applikationen und verschiedene Datenbank-Produkte auf SQL-Injection, Cross-Site-Scripting und sogar Buffer Overflow und erstellen einen Bericht über gefundene Sicherheitslücken[7].

Netzwerksicherheit

Normale Firewalls sind nicht in der Lage SQL-Injection abzuwehren, da ein Angreifer auf die Datenbank nur über ein Web-Frontend mit Formularen zugreift. Firewalls filtern auf Port-Ebene und der Zugriff auf das Frontend mit HTTP (Port 80) ist ja erlaubt.

Application-Level-Firewalls (Proxies), Firewalls mit Intrusion Prevention (IPS) und netzwerkbasierte Intrusion Detection Systeme (IDS) können zwar SQL-Fragmente in Paketen erkennen, daraus aber ein sinnvolles Regelwerk zu erstellen, ist sehr aufwendig, da das schützende System nichts über den aktuellen Kontext weiß. So darf ein spezieller Benutzer bestimmte Datensätze löschen, andere hingegen nicht. Das IDS kann dies aber nicht unterscheiden.

Spezielle Host-basierte Datenbank-Intrusion-Protection-Systeme (DB-IPS) [5,6] versuchen Angriffe auf die Datenbank zu erkennen und abzuwehren. Sie sind auch in der Lage zu unterscheiden, welche Applikation und welche Benutzer bestimmte Befehle an die Datenbank senden dürfen. Einige DB-IPS können sogar Anomalien in der Kommunikation feststellen, beispielsweise wenn eine Applikation plötzlich Kommandos übergibt, die zwar gültig sind, aber vorher noch nie aufgetaucht sind.

Eine vorherige Anmeldung am Server oder der Applikation beseitigt zwar nicht das SQL-Injection-Problem, grenzt doch aber die Zahl möglicher Benutzer ein. Ein Web-Server kann dann Schutz bieten, wenn die Verbindung mit SSL gesichert ist und die Authentifizierung in beide Richtungen erfolgt, also nicht nur der Server ein Zertifikat sendet, sondern auch der Client. Allerdings kann dann aufgrund der Verschlüsselung netzwerk-basierte Intrusion Prevention System nicht mehr in die Pakete hineinschauen; ein eventueller Angriff bliebe unerkannt. Für host-basierte IPS gilt diese Einschränkung nicht.

Eine Anmeldung über ein Webfrontend ist zwar denkbar, da aber meist auch die Anwender-Authentifizierung auf Datenbanken zurückgreift und somit für SQL-Injection anfällig ist, tritt der Fehler nur an anderer Stelle auf.

Fazit

SQL-Injection wird zu einer ernst zu nehmenden Bedrohung für Web-Lösungen die Datenbanken verwenden. Inbesondere Applikationen die mit PHP und ASP entwickelt wurden, scheinen den Statistiken nach besonders anfällig zu sein. Die Zahl der Meldungen von Buffer Overflows in Open-Source-Lösungen und kommerziellen Produkten ist zwar weitaus größer, SQL-Injection-Attacken sind aber viel leichter durchzuführen, da ein Angreifer dazu nur einen Web-Browser benötigt. Programmierer müssen zukünftig sorgfältiger bei der Erstellung von Datenbank-Applikationen sein, um zu vermeiden, dass wichtige Daten kompromittiert werden.

Literatur

[1] Advanced SQL-Injection

[2] More Advanced SQL-Injection

[3] Manipulating SQL-Server using SQL-Injection

[4] Verteidigung gegen SQL-Injection-Angriffe

[5] AppRadar™ for Microsoft SQL Server

[6] Secure Sphere

[7] AppDetective

[8] WebInspect

[9] Extended Stored Procedures Programmer’s Reference

[10] Weitere Lücken mit Cross-Site-Scripting

Kommentarfunktion ist deaktiviert