Mein erster Hack: Eine Site, auf der Sie ein beliebiges Benutzerkennwort festlegen können

Kürzlich habe ich eine interessante Sicherheitslücke gefunden, mit der jeder Benutzer eine bestimmte Site festlegen kann, um ein Kennwort festzulegen. Cool, oder?

Es war lustig und ich dachte, ich könnte einen interessanten Artikel schreiben.

Du bist darauf gestoßen.



Hinweis: Der Autor des übersetzten Artikels ist kein Spezialist für Informationssicherheit, und dies ist sein erster Ausflug in die Welt der SQL-Injection. Er bittet darum, "sich seiner Naivität herablassen".

Warnung: Der Autor des übersetzten Artikels wird die Site mit dieser Sicherheitsanfälligkeit nicht offenlegen. Nicht weil er den Besitzer darüber informiert hat und an Schweigen gebunden ist, sondern weil er die Verwundbarkeit für sich selbst bewahren will. Wenn Sie diese Seite herausfinden, halten Sie bitte den Mund (tsyts).

Sie wissen, auf diese Weise öffnen Sie manchmal eine Website im Toolkit für einen Entwickler, untersuchen minimierten Code und Netzwerkanforderungen ohne Zweck. Und plötzlich merkt man, dass hier etwas nicht stimmt. Gar nicht so. Also habe ich etwas Ähnliches mit der Benutzerprofilseite auf einer der Websites gemacht und festgestellt, dass die Seite beim Ein- und Ausschalten der Empfangsbestätigung eine Netzwerkanforderung sendet:

/api/users?email=no 

Und ich dachte: Ich frage mich, ob sie etwas Dummheit zugelassen haben? Vielleicht sollte ich SQL Injection versuchen?

Ich suchte im Internet nach „xkcd little bobby tables“, um mein Gedächtnis darüber zu aktualisieren, wie man SQL-Injektionen ausführt - ich mag sie nicht - und machte mich an die Arbeit.

Auf der Registerkarte "Chrome-Netzwerk" habe ich die Anforderung kopiert (Kopieren> Als Abruf kopieren) und das Ergebnis in ein Fragment eingefügt, damit die Anforderung abgespielt werden kann:

 fetch('https://blah.com/api/users', { credentials: 'include', headers: { authorization: 'Bearer blah', 'content-type': 'application/x-www-form-urlencoded', 'sec-fetch-mode': 'cors', 'x-csrf-token': 'blah', }, referrer: 'https://blah.com/blah', referrerPolicy: 'no-referrer-when-downgrade', body: 'email=no', // < -- The bit we're interested in method: 'POST', mode: 'cors', }); 

Der Rest des Artikels widmet sich der Auseinandersetzung mit der Körperlinie - es ist ein Transportmittel zum Senden von Anweisungen an den Server.

Zuerst habe ich versucht, meinen Nachnamen zu ändern, indem ich den Wert in der Spalte lastName , wobei ich mich einfach auf den Namen konzentriert habe:

 { // ... body: `email=no', lastName='testing` } 

Es ist nichts Interessantes passiert. Dann habe ich dasselbe mit last_name , dann habe ich mein Glück mit surname versucht - und hoppla! - Die Seite hat meinen Nachnamen durch "Testen" ersetzt.

Es war sehr aufregend. Ich habe SQL-Injektionen immer als eine Art Buchlegende angesehen. Die Tatsache, dass es die Welt nicht wirklich für Code öffnet, der Benutzereingaben direkt in SQL-Ausdrücke einfügt.

Ein bisschen Philosophie
In letzter Zeit gehe ich viele Themen in meinem Leben unter dem Gesichtspunkt des Störgesetzes an: "90% von allem, was um mich herum ist, ist Müll." Mir wurde klar, dass man viele Möglichkeiten verliert, wenn man davon ausgeht, dass alles richtig gemacht wird. Ich denke, dass dieser neu entdeckte Unglaube an die Menschheit mir genug Selbstvertrauen gegeben hat, um dieses Experiment überhaupt aufzunehmen.

Für alle Uneingeweihten werde ich erklären, was das Ergebnis, das ich entdeckt habe, bedeutet.
Ich glaube, dass etwas Ähnliches auf dem Server passiert:

 const userId = someSessionStore.userId; const email = request.body.email; const sql = `UPDATE users SET email = '${email}' WHERE id = '${userId}'`; 

Ich bin sicher, dass ihr Server in PHP geschrieben ist, aber ich spreche diese Sprache nicht, deshalb werde ich Beispiele in JavaScript schreiben. Außerdem bin ich nicht besonders gut in SQL-Abfragen. Ich habe keine Ahnung, ob die Tabelle user oder users oder users user_table , und es spielt keine Rolle.

Wenn meine Benutzer-ID 1234 lautet und ich eine E-Mail = Nein sende, sieht SQL folgendermaßen aus:

 UPDATE users SET email = 'no' WHERE id = '1234' 

Und wenn Sie no durch die Zeichenfolge no', surname = 'testing ersetzen, ist SQL gültig, aber schwierig:

 UPDATE users SET email = 'no', surname = 'testing' WHERE id = '1234' 

Wie Sie sich erinnern, sende ich Anfragen von einem Code-Snippet in den Entwicklertools, während ich mich auf der Profilseite befinde. Von nun an können Sie sich das Feld für den Nachnamen auf dieser Seite (HTML-Element <input>) als eine kleine Standardausgabe vorstellen, in die Sie Informationen schreiben können, indem Sie den Wert für mein Benutzerkonto in der Spalte für den surname in der Datenbank festlegen.

Dann fragte ich mich, ob ich Daten aus einer anderen Spalte in die surname Spalte kopieren könnte.

Ich habe nicht verstanden, was zu tun ist, was mit SQL zu tun ist, und außerdem wusste ich nicht, welche Datenbank auf dem Server verwendet wird. Nach jedem Schritt suchte ich 20 Minuten lang im Internet und kratzte mir dann weitere 20 Minuten am Kopf, weil ich meine Anführungszeichen regelmäßig in die falsche Richtung einfügte. Es ist seltsam, dass ich nicht die gesamte Datenbank zerstört habe.

Das Kopieren von Daten von einer Spalte in eine andere erwies sich als etwas schwieriger, da ich eine solche Anfrage senden wollte (es sollte eine password ):

 UPDATE users SET email = 'no', surname = password WHERE id = '1234' 

Beachten Sie, dass der Code um das password keine Anführungszeichen enthält. Wie Sie sich erinnern, sollte ein hochmoderner Abfrage-Designer so aussehen ...

 const sql = `UPDATE users SET email = '${email}' WHERE id = '${userId}'`; 

... das heißt, wenn Sie versuchen, no', surname = password resultierende Zeichenfolge keine gültige SQL-Abfrage. Stattdessen brauchte ich die gesamte injizierte Zeichenfolge, um der zweite Teil der Anforderung zu werden, und alles, was danach kommt, sollte ignoriert werden. Insbesondere musste ich WHERE und übergeben; am Ende der SQL-Anweisung sowie Kommentar # damit die Informationen rechts davon ignoriert werden. Ja, ich erkläre schrecklich.

Kurz gesagt, ich habe eine neue Zeile gesendet:

 { // ... body: `email=no', surname = password WHERE username = 'me@email.com'; #` } 

Und die folgende Zeile wird an die Datenbank gesendet:

 UPDATE users SET email = 'no', surname = password WHERE username = 'me@email.com'; # WHERE id = '1234' 

Bitte beachten Sie, dass die Datenbank WHERE id = '1234' ignoriert, da dieser Teil nach Kommentar # steht (das Verbot des Kommentierens in SQL-Abfragen scheint ein guter Weg zu sein, um sich vor schlampigem Code zu schützen).

Ich hatte gehofft, mein Passwort P @ ssword1 würde in Textform im Feld Nachname erscheinen, aber stattdessen bekam ich 00fcdde26dd77af7858a52e3913e6f3330a32b31.

Dies enttäuschte mich, obwohl es mich nicht überraschte, und ich versuchte weiterhin, den Hash meines Passworts in die Passwortspalte eines anderen Benutzers zu kopieren.

Lassen Sie mich für Anfänger erklären: Wenn Sie irgendwo ein Konto erstellen und ein neues Passwort P @ ssword1 senden, wird daraus ein Hash wie 00fcdde26dd77af7858a52e3913e6f3330a32b31 und wird in der Datenbank gespeichert. Wenn Sie sich diesen Hash ansehen, kann niemand Ihr Passwort bestimmen (oder so heißt es).

Wenn Sie sich das nächste Mal anmelden und das Kennwort Password @ 1 eingeben, hascht der Server es erneut und vergleicht es mit dem in der Datenbank gespeicherten Hash. Dadurch wird die Konformität bestätigt, ohne dass Ihr Passwort gespeichert werden muss.

Das bedeutet, wenn ich jemandem das Passwort P @ ssword1 geben möchte, muss ich den Wert 00fcdde26dd77af7858a52e3913e6f3330a32b31 in der Passwortspalte dieses Benutzers festlegen.

Leichtgewichtler.

Ich öffnete einen anderen Browser, erstellte einen neuen Benutzer mit einer anderen Mail und überprüfte zunächst, ob ich ihm die Daten einstellen konnte. Die body Eigenschaft wurde aktualisiert:

 { // ... body: `email=no', surname = 'WOOT!!' WHERE username = 'user-two@email.com'; #` } 

Ich habe den Code ausgeführt, die Seite dieses Benutzers aktualisiert und natürlich hat es funktioniert! Jetzt hatte er den Nachnamen "WOOT !!" (Mädchenname meiner Großmutter)

Dann habe ich versucht, ein Passwort für diesen Benutzer festzulegen:

  // ... body: `email=no', password = '00fcdde26dd77af7858a52e3913e6f3330a32b31' WHERE username = 'user-two@email.com'; #` } 

Und weißt du was?!?!?

Es hat nicht funktioniert. Jetzt hatte ich keinen Zugriff auf das zweite Konto.
Es stellte sich heraus, dass ich zwei Fehler gemacht habe, deren Berechnung mehrere Stunden dauerte. Die Experten für Informationssicherheit, die diesen Artikel lesen, haben bereits verstanden, wovon sie sprechen, und lachen wahrscheinlich über den Dummkopf, der seine auf der ersten Seite von Hacking for the Youngest aufgeführten „Exploits“ schreibt.

Nuuuuuu, am Ende habe ich das Netzwerk nach "Passwort-Hash" durchsucht und festgestellt, dass viele Hashes länger sind als meine 00fcdde26dd77af7858a52e3913e6f3330a32b31. Sieht so aus, als würde er irgendwo beschneiden.
Ich habe versucht, einen Text in das Feld für den Nachnamen einzugeben, und ein Limit von 40 Zeichen gefunden (es ist gut, dass das Attribut maxlength für <input> so festgelegt wurde, dass es der Datenbankbeschränkung entspricht).

Jetzt interessierte mich nur noch die ersten 40 Zeichen des Hash, die viel länger sein könnten. Ich suchte nach "SQL-Teilzeichenfolge" und schickte bald die folgende Anfrage an den Server:

 { // ... body: `email=no', surname = SUBSTRING(password, 30, 1000) WHERE username = 'me@email.com'; #` } 

Begonnen mit 30, um sicherzustellen, dass sich die ersten 10 Zeichen mit den letzten 10 Zeichen überschneiden. 00fcdde26dd77af7858a52e3913e6f3330a32b31. Oder die letzten 9. Oder 11.

Lyrischer Exkurs
Ich denke, wenn ich sterbe und zur Hölle fahre, werden sie mich zwingen, alle meine Fehler für immer in Zeitlupe nacheinander zu beobachten. Eine Nahaufnahme zeigt mein Gesicht, während ich immer wieder meine endlose Dummheit erkenne.

Zurück zu den Realitäten: Die Zeichen überlappten sich und wenn ich die Zeilen kombinierte, erhielt ich einen Hash von 64 Zeichen. Wieder habe ich versucht, es auf den zweiten Benutzer zu kopieren:

  { // ... body: `email=no', password = '00fcdde26dd77af7858a52e3913e6f3330a32b3121a61bce915cc6145fc44453' WHERE username = 'user-two@email.com'; #` } 

Und weißt du was?!?!
Nun, Sie haben es erraten, weil ich zwei Fehler erwähnt habe.

Ich konnte mich immer noch nicht beim zweiten Konto anmelden, war aber schon in der Nähe davon (es wäre schön, wenn ich in diesem Moment davon erfahren würde).

Ich suchte nach dem Passwort für die Best Practices-Datenbank und fand / erinnerte mich an so etwas wie „Salz“.

Die Verwendung von Salt bedeutet, dass, wenn Sie für einen Benutzer einen Hash für P @ ssword1 erstellen, für den anderen Benutzer dasselbe Kennwort einen anderen Hash ergibt (ein anderes Salt wird verwendet). Natürlich funktioniert ein Passwort-Hash nicht für zwei Benutzer, die Salze sind unterschiedlich.

Es scheint klug zu sein, aber gleichzeitig dumm. In allen Beispielen in der Tabelle gab es einfach eine andere Spalte namens Salz. Bedeutet dies nicht, dass ich Daten aus zwei Spalten kopieren muss, nicht aus einer? Sieht es nicht aus wie ein zweites Schloss, zu dem der gleiche Schlüssel passt?

Ich habe die Abfrage in der Hoffnung geändert, den Wert aus einer Spalte zu kopieren, die als salt bezeichnet werden könnte.
in die Nachname Spalte:

  { // ... body: `email=no', surname = salt WHERE username = 'myemail@email.com'; #` } 

Ein zufälliger Satz von Zeichen erschien im Nachnamenfeld, ein gutes Zeichen. Um das zu erhalten, was sich als 64-Zeichen-Salz herausstellte, habe ich erneut SUBSTRING verwendet.

Alles war fertig. Ich habe einen Passwort-Hash und das Salt, mit dem er erstellt wurde. Sie müssen ihn nur auf einen anderen Benutzer kopieren. Und ich habe an diesem Abend meine letzte Netzwerkanfrage gesendet:

 fetch('https://blah.com/api/users', { credentials: 'include', headers: { authorization: 'Bearer blah', 'content-type': 'application/x-www-form-urlencoded', 'sec-fetch-mode': 'cors', 'x-csrf-token': 'blah', }, referrer: 'https://blah.com/blah', referrerPolicy: 'no-referrer-when-downgrade', body: `email=no', password = '00fcdde26dd77af7858a52e3913e6f3330a32b3121a61bce915cc6145fc44453', salt = '8b7df143d91c716ecfa5fc1730022f6b421b05cedee8fd52b1fc65a96030ad52' WHERE username = 'user-two@gmail.com'; #`, method: 'POST', mode: 'cors', }); 

Es hat funktioniert! Jetzt konnte ich mich mit einem Passwort vom ersten Konto beim zweiten Konto anmelden.
Ist das nicht verrückt?

* * *

Es gab viele Versuche und Irrtümer, aber wenn ich einen echten Benutzer auswähle, werde ich zuerst sein Salz und Haschisch holen und es bei mir behalten. Dann werde ich seinen gesalzenen Hash durch meinen ersetzen, wie im Artikel beschrieben, mich anmelden und das Salz und den Hash sofort durch die ursprünglichen Werte ersetzen. Ich muss das Passwort einer anderen Person nur für den Bruchteil einer Sekunde ändern, während ich mich anmelde, damit sie mich mit ziemlicher Sicherheit nicht findet.

Theoretisch natürlich. Ich würde das niemals tun.

* * *

Sie fragen sich vielleicht, ob dies eine fiktive Geschichte ist. Nicht erfunden. Ich habe ein paar kleine Details geändert, um mich vor den Anklagen zu schützen, aber alles andere war wie beschrieben. Tatsächlich habe ich die Sicherheitslücke den Eigentümern der Website gemeldet.

Aber ich kann nicht anders, als mich zu fragen, ob dies nur ein Anfängerglück war. Dies ist buchstäblich die erste Site, auf der ich SQL Injection ausprobiert habe, und alles war so, als wäre ich auf mich vorbereitet, als hätte ich die Hacking-Prüfung für Kinder bestanden.

Die von mir beschriebene Site ist klein und hat nur wenige Benutzer (34.718). Dies ist ein kostenpflichtiger Dienst, daher ist er für erfahrene Hacker nicht von Interesse. Und doch fiel mir auf, dass dies möglich war.

Kurz gesagt, jetzt bin ich mit Informationssicherheit auf dieses ganze Thema fixiert. Für mich wurden zwei Lieblingsaktivitäten darin kombiniert: Schreiben eines Codes und Rowdytum. Wenn ich also die „Gehälter für Informationssicherheit in Australien“ google, habe ich einen neuen Job gefunden.
Danke fürs Lesen!

PS: Die Übersetzung des Artikels versucht, den Stil des Autors so weit wie möglich zu bewahren :)

Source: https://habr.com/ru/post/de468695/


All Articles