image: xkcd-327.webp:0:50

SQL Injection

In diesem Kapitel lernst du, wie Angreifer durch geschickte Manipulation von Nutzereingaben in Webformularen Zugriff auf Da­ten­ban­ken erlangen können. Dies funktioniert, wenn die Software, die die Eingaben verar­bei­tet, nicht sorgfältig genug programmiert wur­de. Dazu werden wir uns zuerst eine neue Da­ten­bank für unser Experiment anlegen, die wir dann mit einigen Testdaten füllen.

1. Test-Da­ten­bank anlegen

Erstelle eine neue Da­ten­bank in deinem Work­space-Profil und kopiere den Namen der Da­ten­bank in die Zwischenablage, da wir ihn später benötigen werden. In diesem Tutorial werden wir die Da­ten­bank db_1234 nennen – ersetze diese Bezeichnung also immer durch den Namen deiner Da­ten­bank.

Öffne ein Ter­mi­nal im Work­space und gib den folgenden Befehl ein, um die Testdaten herunterzuladen:

wget https://github.com/specht/workspace-files/raw/main/users.sql

Importiere die Testdaten in deine Da­ten­bank (achte darauf, dass du den Namen deiner neuen Da­ten­bank angibst):

mycli db_1234 < users.sql

Starte anschließend mycli und überprüfe mit Hilfe von SHOW TABLES und einem SELECT-Statement, ob die Daten korrekt importiert wur­den:

login password address
admin YLrelnmPDPB-ZhQ1GzRxiEf 2683 John Calvin Drive, Chicago IL 60603
alice asdf 578 Gordon Street, Claremont CA 91711
bob 1234 525 Cambridge Drive, Phoenix AZ 85039
mallory hunter2 3747 Haven Lane, Lansing MI 48933

2. Test-Programm schreiben

Erstelle eine neue Datei login.rb und füge den folgenden Ruby-Code ein:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
require 'tty-prompt'
require 'mysql2'

PROMPT = TTY::Prompt.new
CLIENT = Mysql2::Client.new(
    host: ENV['MYSQL_HOST'],
    username: ENV['MYSQL_USER'],
    password: ENV['MYSQL_PASSWORD'],
    database: 'db_1234'
)

login = PROMPT.ask('Login:')
password = PROMPT.ask('Password:')

rows = CLIENT.query("SELECT login FROM users \
    WHERE login = '#{login}' \
    AND password = '#{password}' LIMIT 1;").to_a

if rows.empty?
    puts "❌ Fehler: Ungültige Zugangsdaten!"
else
    user = rows.first['login']
    puts "✅ Anmeldung erfolgreich."
    puts "Herzlich willkommen, #{user}."
end

Hinweise:

  • Mit Hilfe von #⁠{ ... } können Variablen in Strings eingebettet werden (Z. 16 und 17) – genauso wie z. B. in Python mit f-Strings. Man nennt diese Technik auch String Interpolation.
  • Das Backslash am Ende einer Zeile (Z. 15 und 16) wird ver­wen­det, um einen String über mehrere Zeilen zu schreiben. Es handelt sich also um einen String ohne Zeilenumbruch, der auf mehrere Zeilen verteilt ist, um die Les­bar­keit zu verbessern.

3. Experimentieren

Notiere dir die SQL-Statements, die aus den folgenden Aufgaben hervorgehen, weil du sie später benötigen wirst.

Aufgabe 1: Teste das Programm und beschreibe kurz die Funktionsweise. Welchen Zweck erfüllt es?
Aufgabe 2: Erläutere die SQL-Abfrage, die dabei zum Einsatz kommt.
Aufgabe 3: Wie in vielen anderen Spra­chen auch, können in SQL Kommentare mit # oder -- eingefügt werden, die bis zum Ende der Zeile gelten und nicht ausgeführt werden.
Im vorliegenden Programm wird die Eingabe des Nutzers unverändert in die SQL-Abfrage eingebettet, womit einem möglichen Angreifer die Möglichkeit gegeben wird, die Abfrage zu manipulieren. Finde eine Eingabe, die es dir er­mög­licht, dich als Administrator anzumelden, ohne das Passwort zu kennen.

Aufgabe 4: Versuche, dich mit dem folgenden Login und einem leeren Passwort anzumelden:
mallory' UNION SELECT address FROM users WHERE LOGIN='mallory' ORDER BY login ASC #
Was kannst du beobachten?

Aufgabe 5: Formuliere die Abfrage so um, dass du das Passwort des Administrators auslesen kannst.

4. Behebung der Sicherheitslücke

Wie du siehst, kann es gefährlich werden, wenn man Nutzereingaben blind vertraut. Es ist daher bei sicherheitskritischen Systemen notwendig, alle Eingaben mit einer gesunden Portion Misstrauen zu betrachten. Das Problem liegt hier auf der Seite der Software, die solche Angriffe zulässt.

Der Angriffsmechanismus, den wir hier ver­wen­det haben, heisst »SQL Injection«, weil ein Angreifer eigenen SQL-Code in eine SQL-Abfrage einschleust und somit das Programm dazu zwingen kann, andere Dinge zu tun, als ur­sprüng­lich vorhergesehen waren. Wir können die Sicherheitslücke leicht schließen, indem wir verhindern, dass Nutzereingaben direkt in die SQL-Abfrage eingefügt werden. Anstatt die Eingabe über String Interpolation direkt einzubinden, können wir ein »Prepared Statement« verwenden. Hierbei wird eine SQL-Abfrage mit Platzhaltern vorbereitet, die dann mit den Nutzereingaben ausgeführt wird. Der SQL-Code wird also nicht direkt mit den Nutzereingaben vermischt, sondern getrennt behandelt.

Kommentiere die Zeilen 15 bis 17 aus und füge dahinter die folgenden Zeilen ein:

query = CLIENT.prepare("SELECT login FROM users WHERE login = ? AND password = ? LIMIT 1;")
rows = query.execute(login, password).to_a
Du kannst Zeilen in Ruby auskommentieren, indem du sie mit einem # beginnen lässt. Schneller geht es, wenn du die Zeilen markierst und dann Strg# drückst (bzw. Ctrl/, falls du ein US-Tastaturlayout verwendest).
Aufgabe 6: Überprüfe, ob das Problem behoben wur­de, indem du die vorherigen Angriffe erneut ausführst:
  1. Versuche, dich als Administrator anzumelden, ohne das Passwort zu kennen.
  2. Versuche, die Adresse von Mallory auszulesen.
  3. Versuche, das Passwort des Administrators auszulesen.

5. Wichtiger Hinweis

Das Eindringen in fremde Com­putersysteme bzw. das Auspähen von Daten ist illegal und wird bestraft. Die hier gezeigten Beispiele dienen ausschließlich der Demonstration von Sicherheitslücken und sollen dazu beitragen, dein Bewusstsein für die Gefahren bei der Pro­gram­mier­ung von Software und der Verarbeitung von Nutzereingaben zu schärfen. Es ist wichtig, dass du dein Wissen verantwortungsbewusst einsetzt und nur auf Systemen experimentierst, für die du die ausdrückliche Erlaubnis hast oder die du selbst betreibst.

Der Zweck dieses Tutorials ist es also nicht, dich dazu zu ermutigen, in fremde Systeme einzudringen oder Daten zu stehlen, sondern dir zu zeigen, worauf du achten musst, um solche Angriffe zu verhindern, wenn du Software implementierst.

Ein kleiner Vergleich zum Schluß: Es ist in Ordnung, wenn du deine Fähigkeiten und ein Lockpicking-Set verwendest, um deine eigenen Schlösser zu öffnen, weil du z. B. den Schlüssel verloren hast. Sobald du allerdings fremde Schlösser ohne Erlaubnis öffnest, begehst du eine Straftat.

Cartoon von Randall Munroe, xkcd.com.