it-swarm.com.de

Wie extrahiere ich Informationen mit einer SQL-Injection auf PHP?

Ich bin neu in SQL-Injektionen. Bitte verzeihen Sie mir, wenn dies ein wenig amateurhaft erscheint.

Ich habe an einer Website gearbeitet, auf der ich die Authentifizierung umgehen und mich als normaler Benutzer anmelden konnte:

username: ' OR 1 -- -
password: <empty>

Die resultierende Seite zeigt "Sie sind angemeldet als: 'OR 1 - -". Also habe ich mir überlegt, ob ich die Datenbankdetails im Feld Benutzername ausgeben könnte, das angezeigt werden soll.

Wenn ich jedoch versuche, durch Aufzählen der Datenbankversion/-informationen einen Schritt weiter zu gehen, funktioniert dies nicht (da ich die Authentifizierung nicht mehr umgehen konnte).

Ich habe folgendes versucht:

username: ' OR 1;SELECT @@VERSION -- -
username: ' OR 1;SELECT user_name -- -

Auf der Website wird der Apache/2.4.7 (Ubuntu) -Server ausgeführt, und ich vermute, dass auf der Datenbank MySQL ausgeführt wird.

7
Nicholas Lim

Es ist kein Problem, neu in der SQL-Injection zu sein (dieses scheint ein ziemlich klassisches Beispiel zu sein und stellt daher einen perfekten Start dar :)), aber ich denke, es ist sehr wichtig, einen zuverlässigen Hintergrund für SQL-Anforderungen zu haben: Syntax, Tabellen, Ergebnismengen , was sind sie und wie werden sie verarbeitet usw.

Das Ziel bei der SQL-Injection ist in der Tat, herauszufinden zu erreichen, was auf der Serverseite vor sich geht, indem die Anforderung auf jede mögliche Weise verdreht wird (keine Fässer, wenn die Situation es verdient!) Und schließlich Erhalten Sie die Fähigkeit, tatsächlich Kontrolle serverseitige Verarbeitung.

Finden Sie eine leicht ausnutzbare SQL-Injection

Zur Veranschaulichung werde ich das Beispiel in der Hauptantwort von " Was ist SQL-Injection? " wiederverwenden. Wenn Sie weitere Hintergrundinformationen zur SQL-Injection benötigen, ist dies möglicherweise ein guter Ausgangspunkt.

Ich werde mir daher vorstellen, dass der Server den folgenden Code ausführt:

sql = "select id, username from users'
      + ' where username='" + username + "' and password='" + password +"'";
  • Nach erfolgreicher Authentifizierung gibt diese Anforderung eine ähnliche Ergebnismenge mit einer einzelnen Zeile zurück:

    +----+----------+
    | id | username |
    +----+----------+
    | 42 | jdoe     |
    +----+----------+
    
  • Bei fehlgeschlagener Authentifizierung wird eine leere Ergebnismenge ohne Zeile erstellt:

    +----+----------+
    | id | username |
    +----+----------+
    +----+----------+
    

Was Sie jetzt bereits geschafft haben und was zeigt, dass der serverseitige Code anfällig ist, besteht darin, die Authentifizierung zu umgehen, indem Sie den Benutzernamen auf ' OR 1 -- Setzen.

Unter Verwendung des gleichen Beispiels wie oben würde der Code die folgende SQL-Anforderung erzeugen:

select id, username from users where username='' OR 1 --

(-- Kommentiert effektiv den Rest der Anforderungszeile aus, die hier daher nicht angezeigt wird)

Hier ist es Zeit zu verstehen, was diese Anfrage tut: Diese Anfrage ruft die ID und den Benutzernamen jedes Benutzers ab (jeder Benutzer mit einem leeren Benutzernamen oder true, was praktisch jeden Benutzer bedeutet).

Dies ist keine gute Nachricht für Sie (es sei denn, die erste Zeile in der Ergebnismenge ist zufällig der Website-Administrator, dies ist jedoch für diesen Beitrag, in dem wir uns auf die Extraktion von Informationen konzentrieren möchten, nicht möglich).

Warum? Auch hier muss man sich vorstellen, was auf der Serverseite vor sich geht.

Dies ist keine gute Nachricht für Sie, da dies bedeutet, dass das, was Sie sehen, auf der Webseite "Sie sind angemeldet" angezeigt wird:

  • Wird nicht aus dem Ergebnis der Datenbankabfrage ($result[0]['username']) Extrahiert, da sonst ein zufälliger Benutzername angezeigt wird.
  • Wird stattdessen höchstwahrscheinlich direkt direkt aus dem vom Server empfangenen Feldwert des Formulars entnommen (getPostData("username");).

In legitimen Anwendungsfällen ändert dies nichts, da beide den gleichen Wert zurückgeben sollten. In unserem Fall ist dies schlecht, da dies bedeutet, dass wir an dieser Stelle höchstwahrscheinlich keine Datenbankinformationen extrahieren und anzeigen können.

Es gibt erweiterte SQL-Injection-Methoden (fehlerbasiertes SQLi, blindes SQLi, ...), mit denen Sie Informationen aus der Datenbank extrahieren können, wenn die Website nicht beabsichtigt, sie explizit anzuzeigen. Sie kommen jedoch mit ihrem eigenen Satz von Problemen und ich werde sie in diesem Beitrag als nicht thematisch betrachten. Angesichts der Sorgfalt des Website-Entwicklers, die Authentifizierungsseite zu sichern, besteht die größte Wahrscheinlichkeit, dass auch andere Seiten anfällig sind. Auf anderen Seiten werden diesmal Informationen angezeigt, die aus der Datenbank abgerufen wurden (die Profilseite des Benutzers ist möglicherweise Ihr bester Freund :) ).

(Wenn einige Seiten mit der Tatsache nicht zufrieden sind, dass Ihr gefälschter Benutzername mehrere Zeilen anstelle einer einzelnen zurückgibt, fügen Sie Ihrem Benutzernamen einfach eine LIMIT -Anweisung hinzu: ' OR 1 LIMIT 1, 1 -- Und von Natürlich können Sie die Parameter ändern, um von einem Benutzer zu einem anderen zu wechseln. Wenn Sie einen gültigen Benutzernamen kennen, können Sie auch versuchen, ihn zu verwenden: admin' --.)

Während ich in diesem Beitrag weiterhin die Authentifizierungsseite als Beispiel nehme, funktioniert dies bei SQL-Injektionen, die sich auf andere Seiten auswirken, genauso.

Bestimmen Sie das Ergebnismengenlayout

Zu Beginn dieses Beitrags haben wir angenommen, dass die Ergebnismenge eine zweispaltige Tabelle mit dem in der zweiten Spalte gespeicherten Benutzernamen ist:

+----+----------+
| id | username |
+----+----------+
| 42 | jdoe     |
+----+----------+

Bis wir wussten, dass dies nur eine Vermutung war und es uns egal war. Leider müssen wir jetzt wissen dies:

  • Die genaue Anzahl der Spalten zu kennen, ist aus technischen Gründen erforderlich, andernfalls ist die Anweisung UNION, die wir später verwenden werden, nicht zufriedenstellend.
  • Wenn wir wissen, aus welcher Spalte der Server die angezeigten Daten abruft, können wir den injizierten Wert an der richtigen Stelle in der Ergebnismenge platzieren (wir müssen nicht den tatsächlichen Spaltennamen kennen, sondern nur seine Position).

(Wenn der Server zufällig PostgreSQL anstelle von MySQL verwendet, müssen Sie möglicherweise sogar die richtigen Spaltentypen ermitteln, um UNION zu erfüllen. Dies ist eher ein Ärgernis als ein echtes Problem, aber es ist etwas, das Sie beachten sollten .)

Bestimmen Sie die Anzahl der Spalten

Am einfachsten ist es, den Server zu bitten, das Ergebnis wie folgt zu bestellen:

username: ' OR 1 ORDER BY 1 -- -

Erhöhen Sie die Klausel ORDER BY 1, Bis eine Fehlermeldung angezeigt wird. Der letzte Arbeitswert entspricht der Anzahl der Spalten in der Ergebnismenge.

Wenn ich dies anhand der Beispielergebnismenge ausführen würde, würde ich Folgendes erhalten:

  • username: ' OR 1 ORDER BY 1 -- -: OK
  • username: ' OR 1 ORDER BY 2 -- -: OK
  • username: ' OR 1 ORDER BY 3 -- -: Fehler, daher enthält die Beispielergebnismenge zwei Spalten.

Bestimmen Sie, welche Spalte (n) verwendbar sind

Nachdem Sie die richtige Anzahl von Spalten kennen, können Sie einen Schritt vorwärts gehen und einen Befehl wie diesen einfügen:

username: ' OR 1 UNION SELECT 1,2 -- -

Wenn Sie drei Spalten hätten, würden Sie SELECT 1,2,3 Für fünf Spalten SELECT 1,2,3,4,5 Usw. ausführen.

Das Ziel hier ist es, die tatsächlichen Zahlen in die Ergebnismenge einzufügen und auf der Webseite (entweder die gerenderte oder deren Quellcode) zu überprüfen, welche vom Server angezeigt wird.

Der Vorteil der Verwendung von Zahlen besteht darin, dass sie von MySQL leicht als Zeichenfolge umgewandelt werden können, wenn der Spaltentyp dies erfordert. Während hier "1,2,3" als Beispiel angegeben ist, können Sie die gewünschten Zahlen verwenden ("1234,2345,3456" würde ebenfalls funktionieren und Ergebnisse liefern, die leichter zu erkennen sind).

Wenn im Beispiel die resultierende Webseite "Sie sind angemeldet als: 2" lautet, wissen Sie, dass der Benutzername aus der zweiten Spalte der Ergebnismenge abgerufen wird.

Achtung : Verstehen Sie, wie der Operator UNION funktioniert: Würde der Server nur die erste Zeile verwenden, die Sie möglicherweise benötigen, um sicherzustellen, dass die linke Seite Anfrage erzeugt keine Zeile!

Tatsächlich verketten die Operatoren UNION zwei Ergebnismengen:

+----+----------+         +----+----------+
| 42 | jdoe     |  UNION  | 1  | 2        |
+----+----------+         +----+----------+

Wird darin enden, dass:

+----+----------+
| id | username |
+----+----------+
| 42 | jdoe     |
+----+----------+
| 1  | 2        |
+----+----------+

Welches möglicherweise nicht das erwartete Ergebnis ist.

Bekommen:

+----+----------+
| id | username |
+----+----------+
| 1  | 2        |
+----+----------+

Möglicherweise möchten Sie sicherstellen, dass die Abfrage auf der linken Seite keinen Eintrag zurückgibt:

username: ' AND 0 UNION SELECT 1,2 -- -

Sehen Sie, wie OR 1 In AND 0 Invertiert wurde, um sicherzustellen, dass die Abfrage auf der linken Seite unabhängig vom Inhalt der Datenbankzeilen als falsch ausgewertet wird.

Nutzen Sie die SQL-Injection

Und jetzt beginnt der Spaß :)!

Mit all diesen Informationen können Sie jetzt die gewünschten Informationen aus der Datenbank extrahieren.

Zum Beispiel:

  • So erhalten Sie die MySQL-Version:

    username: ' AND 0 UNION SELECT 1,@@VERSION -- 
    
  • So rufen Sie Tabellen- und Spaltennamen ab:

    username: ' AND 0 UNION SELECT 1,GROUP_CONCAT(table_name,0x2e,column_name) FROM information_schema.columns WHERE table_schema=database() -- 
    

Es gibt viele Websites, die auf SQL-Anforderungen verweisen, die für die Injektion geeignet sind. Achten Sie auf die Version des MySQL-Servers, die Sie vor sich haben, da einige Syntax möglicherweise versionabhängig sein kann.

Fazit

Die Einfachheit der SQL-Injektion kann abhängig von den Details der SQL-Anforderung des Servers und der auf das Ergebnis angewendeten Verarbeitung stark variieren. Hier hängt alles von Ihrem Glücksfaktor ab.

Das manuelle Erstellen einer erfolgreichen SQL-Injection-Zeichenfolge läuft jedoch immer auf diese Schleife hinaus:

  1. Injizieren Sie eine Anfrage in den Server.
  2. Beobachten Sie das Ergebnis.
  3. Versuchen Sie, dieses Ergebnis aus der Sicht des Servers zu interpretieren: Welche Art der Verarbeitung hätte zu einem solchen Ergebnis führen können? (Hier ist Ihr Wissen über SQL wirklich wichtig)
  4. Erstellen Sie eine neue Injektionszeichenfolge, um entweder Ihr Problem zu lösen, oder überprüfen Sie eine Hypothese zum Verhalten des Servers.
  5. Gehen Sie zurück zu Schritt 1.

Wenn Sie dies manuell tun, ist die Skalierung möglicherweise nicht sehr gut. Abhängig von Ihren Anforderungen finden Sie möglicherweise einige Tools wie sqlmap hilfreich, um all dies zu automatisieren.

12
WhiteWinterWolf

Gestapelte Abfragen in PHP/MySQL

MySQL + PHP unterstützt nur gestapelte Abfragen mit multi_query , was in der Praxis selten verwendet wird.

Deshalb funktioniert Ihr Angriff nicht. Sie versuchen, mit ; Eine zweite Abfrage hinzuzufügen, die einfach nicht unterstützt wird.

Mit dieser Injektion können Sie dennoch Daten extrahieren! Für MySQL gibt es zwei Möglichkeiten: Fehlerbasierte Injektion und Blindinjektion.

Fehlerbasierte Injektion

Erstens funktioniert die fehlerbasierte Injektion, wie der Name schon sagt, nur, wenn die MySQL-Fehlermeldung tatsächlich angezeigt wird.

Die Idee ist einfach: Wenn Sie mit Ihrer Injektion keine Daten extrahieren können, weil die Ergebnisse nicht angezeigt werden, erstellen Sie einfach einen MySQL-Fehler, der die gewünschten Daten enthält. Dies geschieht häufig über den Extraktwert. In Ihrem Fall wäre dies:

' OR extractvalue(1,version()) -- -

Blind SQL Injection (inhaltsbasiert)

Im Vergleich zur fehlerbasierten Injektion funktioniert die blinde Injektion immer, auch wenn keine Fehlermeldungen angezeigt werden.

Die Idee ist wieder einfach. Sie können zwar keine Daten anzeigen, wissen jedoch, ob eine Abfrage Ergebnisse abgerufen hat oder nicht. In Ihrem Fall sind Sie entweder angemeldet (der True-Status) oder nicht (der False-Status).

Ein sehr einfacher Angriff wäre:

' OR substring(version(),1,1)='5

Dies würde wahrscheinlich zu einer erfolgreichen Anmeldung führen, sodass Sie wissen, dass die Datenbankversion mit einer 5 beginnt. Ein anderer Wert würde nicht zu einer Anmeldung führen.

Die Idee ist jetzt, jeweils ein Zeichen abzurufen.

Anstatt nach der Version zu fragen, können Sie auch komplexere Abfragen hinzufügen. Denken Sie daran, Ihr Ergebnis jeweils nur auf eine Zeile LIMIT zu beschränken.

Dieser Angriff ist ziemlich laut, kann aber verbessert werden. Eine Möglichkeit besteht darin, das Zeichen über die Funktion ascii in seinen ASCII-Wert umzuwandeln, mit der Sie Vergleiche mit weniger als und mehr als verwenden können.

Blind SQL Injection (zeitbasiert)

In einigen Fällen erhalten Sie nicht einmal ein wahres/falsches Feedback von Ihrer Injektion. In diesen Fällen können Sie die Antwortzeiten verwenden, um weiterhin wahre/falsche Fragen zu stellen:

' AND if(substring(version(), 1, 1)='5',BENCHMARK(50000000,ENCODE('msg','msg')),null) -- -

Die Idee dabei ist, dass eine teure Funktion aufgerufen wird, wenn eine bestimmte Bedingung erfüllt ist, was zu einer längeren Reaktionszeit führt.

Nach wie vor können Sie komplexere Abfragen ausführen und die Leistung mithilfe der ASCII-Funktion steigern.

2
tim