TL; DR Dieser Artikel beschreibt unpopuläre Tricks für Rennbedingungen, die bei dieser Art von Angriff nicht häufig verwendet werden. Basierend auf den Ergebnissen unserer Forschung haben wir unser eigenes Framework für Racepwn-Angriffe erstellt.
Vasya will 100 Dollar überweisen, die er auf seinem Konto hat, Petya. Er wechselt zur Registerkarte "Überweisungen" und gibt den Spitznamen "Petin" mit dem Betrag an, der überwiesen werden muss - der Nummer 100 -. Klicken Sie anschließend auf die Schaltfläche "Überweisung". Daten, an wen und wie viel an die Webanwendung gesendet wird. Was kann drinnen passieren? Was muss der Programmierer tun, damit alles richtig funktioniert?
- Sie müssen sicherstellen, dass der Betrag Vasya zur Überweisung zur Verfügung steht.
Es ist notwendig, den Wert des aktuellen Guthabens des Benutzers zu ermitteln. Wenn dieser Wert unter dem Betrag liegt, den er überweisen möchte, teilen Sie ihm dies mit. Angesichts der Tatsache, dass unsere Website keine Kredite anbietet und nicht in ein Minusguthaben geraten sollte.
- Subtrahieren Sie den zu überweisenden Betrag vom Guthaben des Benutzers
Es ist notwendig, den Saldo des aktuellen Benutzers unter Abzug des überwiesenen Betrags abzuschreiben. Es war 100, es wurde 100-100 = 0.
- Fügen Sie dem Guthaben des Benutzers Petya den überwiesenen Betrag hinzu.
Petja war im Gegenteil 0, es wurde 0 + 100 = 100.
- Zeigen Sie dem Benutzer eine Nachricht an, dass er gut gemacht ist!
Beim Schreiben von Programmen verwendet eine Person die einfachsten Algorithmen, die sie zu einem einzigen Plot kombiniert, der das Skript des Programms darstellt. In unserem Fall besteht die Aufgabe des Programmierers darin, die Logik der Geldtransfers (Punkte, Gutschriften) von einer Person zur anderen in eine Webanwendung zu schreiben. Anhand der Logik können Sie einen Algorithmus erstellen, der aus mehreren Prüfungen besteht. Stellen Sie sich vor, wir haben einfach alles Unnötige entfernt und einen Pseudocode erstellt.
(. >= _) .=.-_ .=.+_ () ()
Aber alles wäre gut, wenn alles der Reihe nach passieren würde. Eine Site kann jedoch viele Benutzer gleichzeitig bedienen, und dies geschieht nicht in einem Thread, da moderne Webanwendungen Multiprocessing und Multithreading für die parallele Datenverarbeitung verwenden. Mit dem Aufkommen von Multithreading haben Programme eine lustige architektonische Schwachstelle - Race Condition (oder Race Condition).
Stellen Sie sich nun vor, unser Algorithmus arbeitet dreimal gleichzeitig.
Vasya hat immer noch 100 Punkte auf seinem Guthaben, nur irgendwie hat er sich in drei Threads gleichzeitig an die Webanwendung gewandt (mit einer minimalen Zeitspanne zwischen den Anfragen). Alle drei Streams prüfen, ob der Benutzer Petja ist, und prüfen, ob Vasya über genügend Guthaben für die Übertragung verfügt. Zu dem Zeitpunkt, zu dem der Algorithmus den Kontostand überprüft, ist er immer noch gleich 100. Sobald die Überprüfung abgeschlossen ist, werden 100 Mal vom aktuellen Kontostand abgezogen und Pete hinzugefügt.
Was haben wir Vasya hat einen negativen Saldo auf seinem Konto (100 - 300 = -200 Punkte). In der Zwischenzeit hat Petya 300 Punkte, obwohl es tatsächlich 100 sein sollte. Dies ist ein typisches Beispiel für die Ausnutzung einer Rennbedingung. Es ist vergleichbar mit der Tatsache, dass mehrere Personen gleichzeitig einen Pass weitergeben. Unten sehen Sie einen Screenshot dieser Situation von
4lemon
Die Race-Bedingung kann sowohl in Multithread-Anwendungen als auch in den Datenbanken sein, in denen sie arbeiten. Dies ist beispielsweise nicht unbedingt in Webanwendungen ein häufiges Kriterium für die Eskalation von Berechtigungen in Betriebssystemen. Obwohl Webanwendungen ihre eigenen Merkmale für einen erfolgreichen Betrieb haben, möchte ich darauf eingehen.
Typischer Betriebszustand
Ein Hacker betritt einen Shisha-Raum, eine Quest und eine Bar, und für ihn - Sie haben eine Rennbedingung! Omar Ganiev
In den meisten Fällen wird Multithread-Software als Client verwendet, um den Rennzustand zu überprüfen / zu betreiben. Zum Beispiel Burp Suite und sein Intruder-Tool. Sie stellen eine HTTP-Anforderung zur Wiederholung, installieren viele Streams und schalten die Flut ein. Wie zum Beispiel
in diesem Artikel . Oder
in diesem . Dies ist eine ziemlich funktionierende Methode, wenn der Server die Verwendung mehrerer Threads für seine Ressource zulässt. Wenn dies nicht funktioniert, versuchen Sie es erneut, wie in den obigen Artikeln angegeben. Tatsache ist jedoch, dass dies in einigen Situationen möglicherweise nicht effektiv ist. Insbesondere, wenn Sie sich daran erinnern, wie solche Anwendungen auf den Server zugreifen.
Was ist da auf dem Server
Jeder Thread stellt eine TCP-Verbindung her, sendet Daten, wartet auf eine Antwort, schließt die Verbindung, öffnet erneut, sendet Daten usw. Auf den ersten Blick werden alle Daten gleichzeitig gesendet, aber HTTP-Anforderungen selbst kommen möglicherweise nicht synchron an und sind aufgrund der Art der Transportschicht, der Notwendigkeit, eine sichere Verbindung (HTTPS) herzustellen und DNS (nicht im Fall von Burp) aufzulösen, und vieler Schichten inkonsistent Abstraktionen, die Daten übergeben, bevor sie an ein Netzwerkgerät gesendet werden. Wenn es um Millisekunden geht, kann dies eine Schlüsselrolle spielen.
HTTP-Pipelining
Sie können HTTP-Pipelining aufrufen, bei dem Sie Daten mit einem einzigen Socket senden können. Sie können selbst sehen, wie es mit dem Dienstprogramm netcat funktioniert (Sie haben GNU / Linux, oder?).
Tatsächlich müssen Sie aus vielen Gründen Linux verwenden, da es einen moderneren TCP / IP-Stack gibt, der von den Betriebssystemkernen unterstützt wird. Der Server ist höchstwahrscheinlich auch darauf.Führen Sie beispielsweise
nc google.com 80 aus und fügen Sie die Zeilen dort ein
GET / HTTP/1.1 Host: google.com GET / HTTP/1.1 Host: google.com GET / HTTP/1.1 Host: google.com
Somit werden innerhalb einer Verbindung drei HTTP-Anforderungen gesendet und Sie erhalten drei HTTP-Antworten. Diese Funktion kann verwendet werden, um die Zeit zwischen Anforderungen zu minimieren.
Was ist da auf dem Server
Der Webserver empfängt nacheinander Anforderungen (Schlüsselwort) und verarbeitet die Antworten in der Reihenfolge ihrer Priorität. Diese Funktion kann verwendet werden, um in mehreren Schritten anzugreifen (wenn zwei Aktionen nacheinander in der Mindestzeit ausgeführt werden müssen) oder um beispielsweise den Server bei der ersten Anforderung zu verlangsamen, um den Erfolg des Angriffs zu erhöhen.
Trick - Sie können verhindern, dass der Server Ihre Anforderung verarbeitet, indem Sie sein DBMS laden, insbesondere wenn INSERT / UPDATE verwendet wird. Schwere Anfragen können Ihre Ladung „verlangsamen“, daher ist es wahrscheinlicher, dass Sie dieses Rennen gewinnen.
Aufteilen einer HTTP-Anfrage in zwei Teile
Denken Sie zunächst daran, wie die HTTP-Anforderung generiert wird. Wie Sie wissen, ist die erste Zeile die Version für Methode, Pfad und Protokoll:
GET / HTTP/1.1
Als nächstes folgen die Überschriften vor dem Zeilenumbruch:
Host: google.com
Cookie: a=1
Aber woher weiß der Webserver, dass die HTTP-Anforderung beendet wurde?
Schauen wir uns ein Beispiel an, geben Sie
nc google.com 80 ein und dort
GET / HTTP/1.1
Host: google.com
GET / HTTP/1.1
Host: google.com
, nachdem Sie die EINGABETASTE gedrückt haben, geschieht nichts. Klicken Sie erneut - Sie sehen die Antwort.
Das heißt, damit der Webserver die HTTP-Anforderung akzeptiert, sind zwei Zeilenvorschübe erforderlich. Eine gültige Abfrage sieht folgendermaßen aus:
GET / HTTP/1.1\r\nHost: google.com\r\n\r\n
Wenn dies die POST-Methode wäre (Inhaltslänge nicht vergessen), würde die richtige HTTP-Anforderung folgendermaßen aussehen:
POST / HTTP/1.1
Host: google.com
Content-Length: 3
a=1
oder
POST / HTTP/1.1\r\nHost: google.com\r\nContent-Length: 3\r\n\r\na=1
Versuchen Sie, eine ähnliche Anfrage über die Befehlszeile zu senden:
echo -ne "GET / HTTP/1.1\r\nHost: google.com\r\n\r\n" | nc google.com 80
Als Ergebnis erhalten Sie eine Antwort, da unsere HTTP-Anfrage abgeschlossen ist. Wenn Sie jedoch das letzte \ n Zeichen entfernen, erhalten Sie keine Antwort.
Tatsächlich müssen viele Webserver nur \ n als Übertragung verwenden. Daher ist es wichtig, \ r und \ n nicht zu tauschen, da andere Tricks möglicherweise nicht funktionieren.
Was gibt es? Sie können gleichzeitig viele Verbindungen zu einer Ressource öffnen, 99% Ihrer HTTP-Anfrage senden und das letzte Byte nicht gesendet lassen. Der Server wartet, bis Sie den letzten Zeilenvorschub erreicht haben. Nachdem klar ist, dass der Hauptteil der Daten gesendet wurde, senden Sie das letzte Byte (oder mehrere).
Dies ist besonders wichtig, wenn es sich um eine große POST-Anforderung handelt, z. B. wenn ein Datei-Upload erforderlich ist. Aber auch bei einer kleinen Anfrage ist dies sinnvoll, da die Bereitstellung einiger Bytes viel schneller ist als die gleichzeitige Bereitstellung von Kilobyte an Informationen.
Verzögerung vor dem Senden des zweiten Teils der Anfrage
Nach den Untersuchungen von
Vlad Roskov ist es nicht nur notwendig, die Anfrage aufzuteilen, sondern es ist auch sinnvoll, zwischen dem Senden des Hauptteils der Daten und dem endgültigen Teil eine Verzögerung von mehreren Sekunden zu machen. Und das alles, weil Webserver Anfragen analysieren, noch bevor sie vollständig empfangen werden.

Was ist da auf dem Server
Wenn Sie beispielsweise HTTP-Anforderungsheader empfangen, analysiert nginx diese und speichert die fehlerhafte Anforderung zwischen. Wenn das letzte Byte eintrifft, nimmt der Webserver die teilweise verarbeitete Anforderung und sendet sie direkt an die Anwendung, wodurch die Verarbeitungszeit von Anforderungen verkürzt wird, was die Wahrscheinlichkeit eines Angriffs erhöht.
Wie man damit umgeht
Zuallererst ist dies natürlich ein Architekturproblem. Wenn Sie eine Webanwendung richtig entwerfen, können Sie solche Rennen vermeiden.
In der Regel werden die folgenden Angriffskontrollmethoden verwendet:
Die Operation blockiert den Zugriff auf das gesperrte Objekt im DBMS, bis es entsperrt wird. Andere stehen und warten am Rande. Es ist notwendig, mit Schlössern richtig zu arbeiten, um nichts Überflüssiges zu blockieren.
Ordentliche Transaktionen (serialisierbar) - Stellen Sie sicher, dass die Transaktionen streng nacheinander ausgeführt werden. Dies kann jedoch die Leistung beeinträchtigen.
Nehmen Sie etwas (z. B. etcd). Zum Zeitpunkt des Aufrufs der Funktionen wird ein Eintrag mit einem Schlüssel erstellt. Wenn es nicht möglich war, einen Eintrag zu erstellen, ist dieser bereits vorhanden und die Anforderung wird unterbrochen. Am Ende der Bearbeitung der Anfrage wird der Datensatz gelöscht.
Und im Allgemeinen gefiel mir das
Video von Ivan the Hard Worker über Sperren und Transaktionen, sehr informativ.
Sitzungsmerkmale im Rennzustand
Eines der Merkmale der Sitzungen kann sein, dass es an sich die Ausbeutung des Rennens stört. In PHP wird beispielsweise nach session_start () eine Sitzungsdatei gesperrt, und das Entsperren erfolgt nur am Ende des Skripts (wenn
session_write_close nicht
aufgerufen wurde ). Wenn in diesem Moment ein anderes Skript aufgerufen wird, das eine Sitzung verwendet, wird es warten.
Um diese Funktion zu umgehen, können Sie einen einfachen Trick verwenden - um sich so oft wie nötig zu authentifizieren. Wenn Sie mit der Webanwendung viele Sitzungen für einen Benutzer erstellen können, erfassen Sie einfach die gesamte PHPSESSID und machen Sie jede Anforderung zu einer eigenen Kennung.
Nähe zum Server
Wenn die Site, auf der Sie die Rennbedingung betreiben möchten, in AWS gehostet wird, nehmen Sie das Auto in AWS. Wenn in DigitalOcean - nehmen Sie es dort.
Wenn die Aufgabe darin besteht, Anforderungen zu senden und das Sendeintervall zwischen ihnen zu minimieren, ist die unmittelbare Nähe zum Webserver zweifellos von Vorteil.
Immerhin gibt es einen Unterschied beim Ping an den Server 200 und 10 ms. Und wenn Sie Glück haben, können Sie sogar auf demselben physischen Server landen, dann ist das Fliegen ein bisschen einfacher :)
Zusammenfassend
Für eine erfolgreiche Rennbedingung können Sie verschiedene Tricks anwenden, um die Erfolgswahrscheinlichkeit zu erhöhen. Senden Sie mehrere Keep-Alive-Anforderungen in einer, wodurch der Webserver verlangsamt wird. Teilen Sie die Anfrage in mehrere Teile auf und erstellen Sie vor dem Senden eine Verzögerung. Reduzieren Sie die Entfernung zum Server und die Anzahl der Abstraktionen zur Netzwerkschnittstelle.
Als Ergebnis dieser Analyse haben wir zusammen mit Michail Badin das
RacePWN- Tool entwickelt
Es besteht aus zwei Komponenten:
- C librace-Bibliothek, die in kürzester Zeit viele HTTP-Anforderungen an den Server sendet und die meisten Funktionen des Artikels verwendet
- Dienstprogramme Racepwn, die die JSON-Konfiguration akzeptieren und diese Bibliothek im Allgemeinen steuern
RacePWN kann in andere Dienstprogramme integriert werden (z. B. in Burp Suite) oder Sie können eine Weboberfläche für die Verwaltung von Flügen erstellen (Sie können es immer noch nicht in die Hände bekommen). Viel Spaß!
In Wirklichkeit gibt es jedoch noch Raum für Wachstum, und Sie können sich an HTTP / 2 und seine Aussichten auf Angriffe erinnern. Im Moment haben HTTP / 2, die meisten Ressourcen jedoch nur Front-Proxy-Anforderungen an das gute alte HTTP / 1.1.
Vielleicht kennen Sie noch andere Feinheiten?
Das Original