Filterausdrücke können schnell gefährlich werden, zumindest wenn Sie nicht aufpassen. Dafür sorgt die sogenannte SQL-Injection, bei der davon ausgegangen wird, dass der Benutzer einen Ausdruck in die Benutzeroberfläche eingibt, die dann als Teil eines SQL-Ausdrucks verwendet wird und Schaden anrichten könnte. Dieser Beitrag erläutert, was SQL-Injection eigentlich ist und wie Sie sich dagegen wappnen können.
Die SQL-Injection ist eine Methode, aus eigentlich harmlosen SQL-Anweisungen schädliche SQL-Anweisungen zu machen – oder damit an Informationen zu gelangen, die der Benutzer normalerweise nicht erhalten sollte. Dabei ist die Voraussetzung immer ein Eingabefeld in einer Webseite oder, in diesem Falle, in einem Access-Formular, in das der Benutzer beispielsweise einen Vergleichswert für einen Vergleichsausdruck eingeben soll – im einfachsten Fall etwa die ID eines Kunden.
Beispielformular
Als Spielwiese für die folgenden Beispiele verwenden wir ein einfaches Access-Formular namens frmKunden samt Unterformular sfmKunden, wobei das Unterformular die Daten der Tabelle tblKunden anzeigt und das Hauptformular ein Eingabefeld für einen Suchbegriff liefert, der zunächst die Suche nach Werten im Feld KundeID ermöglicht (s. Bild 1).
Bild 1: Beispielformular in der Entwurfsansicht
Für das Textfeld txtKundenID legen wir die folgende Ereignisprozedur an:
Private Sub txtKundenID_AfterUpdate() Dim strSQL As String strSQL = "SELECT * FROM tblKunden WHERE KundeID = " & Me!txtKundenID Me!sfmKunden.Form.RecordSource = strSQL End Sub
Diese sorgt dafür, dass eine neue SQL-Anweisung zusammengestellt wird, die den Datensatz der Tabelle tblKunden mit einem bestimmten Wert im Feld KundeID liefert und diesen der Eigenschaft RecordSource des Unterformular zuweist. Dies gelingt auch reibungslos, wenn der Benutzer einfach nur einen Zahlenwert eingibt (s. Bild 2).
Bild 2: Filtern nach dem Wert 1 im Feld KundeID
Ergebnismenge mit UNION erweitern
Was an unserer Methode soll also gefährlich sein – schließlich kann man doch nur Zahlenwerte eingeben und erhält das gewünschte Ergebnis Mitnichten: Schon die Kenntnis der Datenstruktur (oder ein wenig experimentelle Arbeit) erlauben es, ein ganz anderes Ergebnis zu erhalten als geplant. Geben Sie beispielsweise einmal den folgenden Ausdruck in das Suchfeld ein:
1 UNION SELECT * FROM tblKunden WHERE KundeID = 10
Das Ergebnis ist überraschend: Es gibt keinen Fehler, sondern es erscheinen zwei Datensätze (s. Bild 3)! Allzu verwunderlich ist dies jedoch gar nicht, denn der resultierende Ausdruck in der Variablen strSQL, den Sie sich einfach im Debugbereich ausgeben lassen können, sieht wie folgt aus:
Bild 3: Erweiterung des Ergebnisses per UNION
SELECT * FROM tblKunden WHERE KundeID = 1 UNION SELECT * FROM tblKunden WHERE KundeID = 11
Gut, der Schaden ist gering, denn wir greifen so nur auf Datensätze zu, die wir auch anderweitig hätten lesen können. Aber wenn wir keine Bedingung angeben, liefert der resultierende SQL-Ausdruck uns sogar wieder alle Datensätze:
SELECT * FROM tblKunden WHERE KundeID = 1 UNION SELECT * FROM tblKunden
Dies könnte interessant werden, wenn der eigentliche Filterausdruck noch dafür sorgt, dass beispielsweise nur die Kunden des aktuell angemeldeten Mitarbeiters angezeigt werden sollen.
Daten anderer Tabellen auslesen
Noch spannender wird es, wenn Sie hinter dem UNION -Schlüsselwort nicht auf die gleiche, sondern direkt auf eine andere Tabelle zugreifen, die unter Umständen kritische Daten enthält. Im Beispiel wollen wir einfach einmal probieren, Daten aus der Tabelle tblArtikel zusätzlich auszugeben, und testen den folgenden Suchausdruck:
1 UNION SELECT ArtikelID as KundeID, Artikelname as Kundencode FROM tblArtikel
Dies liefert im ersten Anlauf noch den Fehler aus Bild 4, weil der zweite Teil der UNION-Abfrage nicht die gleiche Anzahl Felder wie der erste Teil zurückliefert. Aber was geschieht, wenn wir einfach ein paar fixe Werte entsprechend der Anzahl der Felder des ersten Teils der Abfrage hinzufügen – und so notfalls durch Experimentieren auf die richtige Anzahl Felder kommen So ergibt sich nach kurzem Test der folgende Ausdruck:
Bild 4: Fehler, wenn nicht alle Seiten der UNION-Abfrage die gleiche Anzahl Felder enthalten
1 UNION SELECT ArtikelID as KundeID, Artikelname as Kundencode, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 FROM tblArtikel
Das Ergebnis ist einigermaßen erschreckend: Wir gelangen so tatsächlich an die Informationen der Tabelle tblArtikel, die eigentlich gar nicht zur Datenherkunft des Unterformulars gehört (s. Bild 5). Und natürlich könnten wir diese Tabelle auch durch eine Tabelle mit wirklich sensiblen Daten wie etwa der Benutzertabelle ersetzen.
Bild 5: Ermitteln der Daten einer anderen Tabelle
So lassen sich auch leicht die Tabellen einer Datenbank ausgeben – und zwar mit diesem Suchbegriff:
1 UNION SELECT Name, Type , 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 FROM MSysObjects WHERE Type = 1
Gefährliche Strings
Gibt es ebenfalls Probleme, wenn man ein Suchfeld für die Eingabe einer Zeichenkette verwendet Ja. Dazu fügen wir dem Formular ein Textfeld namens txtFirma hinzu, das nach dem Aktualisieren die folgende Ereignisprozedur auslöst:
Private Sub txtFirma_AfterUpdate() Dim strSQL As String strSQL = "SELECT * FROM tblKunden WHERE Firma LIKE ''" & Me!txtFirma & "''" Debug.Print strSQL Me!sfmKunden.Form.RecordSource = strSQL End Sub
Geben Sie hier etwa A* ein, liefert dies alle Einträge der Tabelle tblKunden, deren Feld Firma einen Wert enthält, der mit A beginnt. Wie können wir hier beispielsweise mit dem UNION-Schlüsselwort auf die Daten einer zweiten Tabelle wie etwa MSysObjects zugreifen Dazu fügen wir einfach hinter dem Vergleichswert ein schließendes Hochkomma ein und hängen die UNION-Klausel hinten an:
A*'' UNION SELECT Name, Type, 3,4,5,6,7,8,9,10,11,12 FROM MSysObjects
Dies liefert uns, wie in Bild 6 zu erkennen, wie gewünscht alle Tabellennamen der Datenbank. Der resultierende SQL-Ausdruck endet nun zwar mit einem unerwünschten Hochkomma, doch das hindert Access nicht an seiner Interpretation:
Bild 6: Ermitteln der Daten einer anderen Tabelle, hier die Tabellennamen
SELECT * FROM tblKunden WHERE Firma LIKE ''A*'' UNION SELECT Name, Type, 3,4,5,6,7,8,9,10,11,12 FROM MSysObjects''
Würde der SQL-Ausdruck wie folgt zusammengesetzt, also mit einem Sternchen am Ende, würde dies allerdings einen Fehler auslösen:
strSQL = "SELECT * FROM tblKunden WHERE Firma LIKE ''" & Me!txtFirma & "*''"
Der resultierende SQL-Ausdruck würde nämlich dann wie folgt aussehen:
Ende des frei verfügbaren Teil. Wenn Du mehr lesen möchtest, hole Dir ...
den kompletten Artikel im PDF-Format mit Beispieldatenbank
diesen und alle anderen Artikel mit dem Jahresabo