SQL Injection: Gezielte Maßnahmen statt Block Lists

Bei Schwachstellen im Web nimmt SQL Injection nach wie vor eine führende Rolle ein, dabei ist die Abwehr gar nicht schwer.

In Pocket speichern vorlesen Druckansicht 109 Kommentare lesen

(Bild: Photon photo/Shutterstock.com)

Lesezeit: 15 Min.
Von
  • Matthias Altmann
Inhaltsverzeichnis

Webangriffe nutzen vor allem zwei Lücken aus: SQL Injection und Local File Inclusion, wobei SQL Injection wohl nach wie vor mit mehr als zwei Drittel aller Webanwendungsangriffe eine klare Mehrheit hat. Das lässt sich unter anderem Studien zu Angriffen auf Webapplikationen und zu Attacken auf Finanzdienste entnehmen.

Auch die OWASP als Herausgeber der Top 10 der riskantesten Web-Lücken, listet in der nach wie vor jüngsten Ausgabe von 2017 SQL Injection auf Platz 1. Obwohl das Phänomen seit mehr als 20 Jahren bekannt ist, scheint es nicht kleinzukriegen zu sein.

Um der Bedrohung Herr zu werden, setzen zahlreiche Softwarehersteller und -betreiber bis heute auf sogenannte Block Lists (ehemals Blacklists), die No-Gos für eintreffende Eingaben von außen vorgeben. Sie dienen als Basis, um die Eingaben entweder zu blocken, Teile davon abzuändern oder zu entfernen. Das Vorgehen arbeitet als Filter, damit am Ende keine bösartigen Queries die Datenbank erreichen.

Dieses Vorgehen ist zumeist relativ einfach umzusetzen, da es zunächst nur kleine Veränderungen an der Anwendung vornimmt und keinen tiefen Eingriff bedeutet. Die Seiteneffekte für den reibungsfreien Betrieb der bestehenden Software sind überschaubar. Es kann allerdings höchstens ein kurzfristiger Ansatz sein, denn die aufgestellten Blockaden können erfahrene Angreifer leicht umgehen.

Eine Demoanwendung hilft dabei, die Wege nachzuvollziehen. Der folgende Code ist eine Java-Spring-Boot-Anwendung (Version 2.3.8) auf Basis von JDBC mit einem Maria-DB-Backend. Sie lässt sich aber ebenso in jeder anderen Kombination aus einer relationalen Datenbank mit Framework oder Programmiersprache umsetzen, die die Nutzereingaben als String zu einer Query zusammenfügt und anschließend an die Datenbank sendet. Der Beispielcode findet sich auf GitHub. Entwicklerinnen und Entwickler können ihn mit Git klonen und im Verzeichnis sqli_victim_webapp_java einsehen und testen.

Die Spring-Anwendung hat mehrere REST-Endpunkte, und einer davon ist /vulnbyid. Dabei gibt der aufrufende Client als Request-Parameter eine ID mit, die einen Datenbankeintrag zurückliefert. Der Code setzt die Abfrage zunächst folgendermaßen zusammen:

"SELECT * FROM user WHERE id = '" + id + 
  "' GROUP BY username ORDER BY username ASC"

Im Anschluss gibt der Code diesen String an die Datenbank weiter:

Connection c = dataSource.getConnection();
rs = c.createStatement().
  executeQuery(blacklist.getBlacklistedQuery());

Per Kommandozeile sieht eine Abfrage dieses Endpunkts folgendermaßen aus:

curl localhost:5808/sqlidemo/vulnbyid -d id=1

Dass id nutzerseitig kontrollierbar ist und in die Query einfließt, ermöglicht eine typische SQL Injection. Angreifer beenden den aktuellen id-String und fügen ihren eigenen Teil hinzu, beispielsweise mit

1' AND 1=1 --

das ergibt folgende Anfrage:

"SELECT * FROM user WHERE id = '1' AND 1=1 \
  -- '" + "GROUP BY username ORDER BY username ASC"

-- leitet im SQL-Standard einen Kommentar ein. Das ist ein gängiges Mittel, um alles abzuschneiden, was danach kommt. Den Rest der Zeile ignoriert die Datenbank somit, und in der Attacke lassen sich Fehler vermeiden.

1' AND 1=1 -- ` vs `1' AND 1=0 --

Durch die AND-Verknüpfung prüft der Angriff, ob eine Injection möglich ist. Dabei nutzt er Boolesche Algebra: Der erste Teil sollte dieselbe Rückmeldung liefern wie ein einfaches id=1, und der zweite Teil sollte kein Ergebnis ausgeben.

Ist verifiziert, dass eine Injection funktioniert, durchläuft der Angriff zwei Identifikationsphasen:

  1. Datenbank
  2. Angriffsmuster

Über ein Feedback lässt sich die Datenbank ermitteln. Das kann in Form von Fehlermeldungen passieren oder anhand positiver Syntax, die erfolgreich durchläuft.

Ist die Datenbank bestimmt, gibt es fünf unterschiedliche Angriffsmuster:

  1. Error-Based versucht anhand logischer Fehler zu arbeiten und beispielsweise beim Fehlschlag einer Bedingung Informationen zu extrahieren.
  2. Blind hat keinen Rückgabekanal, läuft also blind und etwa über die Sleep-Funktion auf der Datenbank und if-Bedingungen, um einzelne Zeichen zu ermitteln.
  3. In Batched kann die Datenbank Befehle aufeinanderfolgend ausführen, etwa getrennt durch ein Semikolon in dem Injection-String.
  4. Die Methode Inline nutzt SELECTs in dem FROM Teil einer Query.
  5. Union-Based ist schließlich die Verschmelzung zweier Tabellen in einem Ergebnis. Dadurch lässt sich eine legitime Rückmeldung mit Daten zusammensetzen, auf die Nutzer keinen Zugriff haben dürften.

Da bei dem für die Demoanwendung ausgewählten MySQL Angriffe über Batched und Inline Queries zumeist nicht möglich sind, Error-based oftmals nicht leicht anwendbar ist und Blind zu den komplexeren Mustern gehört, geht der Text im Folgenden beispielhaft nur auf das Angriffsmuster Union-Based ein.

Web Application Security auf der heise devSec

Dieses Jahr erweitern heise Developer, heise Security und dpunkt.verlag die Konferenz für sichere Softwareentwicklung heise devSec um drei Thementage, und am 1. Juli dreht sich alles um das Thema Web Application Security. Zuvor steht am 29. Juni DevSecOps im Fokus. Für beide Tage gilt derzeit noch der Frühbucherrabatt.

Wer einen Vortrag zu sicherer Softwareentwicklung halten möchte, sollte noch bis zum 13. Juni den Call for Proposals zu der im Herbst veranstalteten zweitägigen heise devSec nutzen.

Der Angriff ermittelt zunächst die Anzahl der Spalten, indem die Anfrage so lange ORDER BY erhöht, bis eine Fehlermeldung erscheint:

curl localhost:5808/sqlidemo/vulnbyid -d \
  id="1' ORDER BY 1 -- "
curl localhost:5808/sqlidemo/vulnbyid -d \
  id="1' ORDER BY 2 -- "
curl localhost:5808/sqlidemo/vulnbyid -d \
  id="1' ORDER BY 3 -- "
curl localhost:5808/sqlidemo/vulnbyid -d \
  id="1' ORDER BY 4 -- "

Unknown column '4' in 'order clause'

Somit existieren drei Spalten. Nun ist die Frage, welche davon in der Rückmeldung auf der Webseite zu sehen ist. In der Demoanwendung ist es die Spalte 3:

Die dritte Spalte ist als Rückkanal ermittelt.

An dieser Stelle kann die Angriffsseite Daten extrahieren. Nachfolgend dient als Beispiel das Auslesen der Datenbankversion:

curl localhost:5808/sqlidemo/vulnbyid -d \
id="1' UNION SELECT NULL,NULL,(@@VERSION) -- "

Die Versionsnummer der Datenbank ließ sich über Union Select auslesen.