Skip to main content

Atténuer les attaques par DOM clobbering en JavaScript

Écrit par

Keshav Malik

7 août 2023

0 minutes de lecture

Le Document Object Model (DOM) fait office d’interface entre HTML et JavaScript, en faisant le lien entre le contenu statique et l’interactivité dynamique. Cette fonction rend le DOM indispensable aux développeurs web modernes.

Cependant, le DOM présente un piège : le DOM clobbering. Ce phénomène se produit lorsque des éléments HTML entrent en conflit avec des variables ou des fonctions JavaScript globales, ce qui peut entraîner un comportement inattendu et créer une faille de sécurité dans votre application web.

Pour résoudre ce problème, il faut adopter des pratiques de programmation rigoureuses, bien connaître JavaScript et maîtriser la sécurité des applications web. Cet article présente le concept de DOM clobbering et propose des stratégies pour créer des applications web plus sûres et plus robustes.

Comment se produit le DOM clobbering

Chaque élément HTML peut avoir un attribut ID ou name unique, qui permet de le retrouver facilement dans JavaScript. Par exemple, le bouton HTML suivant possède un attribut ID dont la valeur est "myButton" :

<button id="myButton">Click Me!</button>

Nous pouvons utiliser cet ID dans notre code JavaScript pour manipuler le bouton, par exemple en modifiant son texte lorsqu’on clique dessus :

document.getElementById('myButton').onclick = function() {
    this.textContent = 'Clicked!';
};

Le DOM facilite cette interaction entre HTML et JavaScript. Mais que se passe-t-il si l’attribut ID ou name de l’élément HTML entre en conflit avec une variable ou une fonction JavaScript globale ?

Lorsqu’un navigateur charge une page HTML, il crée automatiquement des variables JavaScript globales pour chaque attribut ID et name du DOM HTML. Si un élément HTML porte le nom "myButton", le navigateur crée une variable JavaScript globale, myButton, qui fait référence à cet élément HTML.

Imaginons maintenant que nous ayons déclaré une fonction JavaScript nommée myButton :

function myButton() {
    // some code
}

Mais nous avons également un élément HTML dont l’ID ou le nom est "myButton". Au chargement de la page, le processus automatique du navigateur remplace la fonction JavaScript myButton par une référence à l’élément HTML :

<button id="myButton">Click Me!</button>

console.log(myButton); // This will log the HTML button element, not the function

Ce processus s’appelle le DOM clobbering. Il peut entraîner un comportement imprévisible et des vulnérabilités de sécurité. Si un attaquant peut contrôler les attributs, il peut injecter du code malveillant dans la page web et provoquer des problèmes de sécurité comme le cross-site scripting (XSS).

Prenons l’exemple suivant :

Enter your name: <input id="username" type="text">
<button onclick="greet()">Greet</button>

function greet() {
    var username = document.getElementById('username').value;
    alert(`Hello ${username}`);
}

Un attaquant pourrait saisir quelque chose comme <img id='alert' src=x onerror='alert(1)'>, créant ainsi un nouvel élément HTML dont l’'id' est 'alert'. Cet élément remplace — ou « clobber » — la fonction alert habituelle de JavaScript. La prochaine fois que le site web tentera d’utiliser cette fonction, elle ne fonctionnera pas correctement et pourrait même exécuter du code malveillant.

Repérer les vulnérabilités liées au DOM clobbering

Prenons une application web simple dotée d’un formulaire de commentaires. Les utilisateurs saisissent leur nom et un commentaire, puis envoient le formulaire. La page affiche alors le commentaire :

HTML :

<h2>Feedback Form</h2>
<form>
  <label for="name">Name:</label><br>
  <input type="text" id="name" name="name"><br>
  <label for="feedback">Feedback:</label><br>
  <textarea id="feedback" name="feedback"></textarea><br>
  <input type="submit" value="Submit">
</form>

<div id="feedbackDisplay"></div>

JavaScript :

document.querySelector('form').onsubmit = function(event) {
    event.preventDefault();

    let name = document.getElementById('name').value;
    let feedback = document.getElementById('feedback').value;

    let feedbackElement = document.getElementById('feedbackDisplay');

    feedbackElement.innerHTML = `<p><b>${name}</b>: ${feedback}</p>`;
};

Ce code récupère le nom et le commentaire de l’utilisateur, puis les affiche dans un élément de paragraphe à l’intérieur de la div feedbackDisplay.

Un attaquant pourrait exploiter ce code en envoyant du HTML dans le formulaire de commentaires. Par exemple, s’il saisissait le code suivant dans le champ name et envoyait le formulaire, la zone d’affichage des commentaires serait remplacée par la balise script :

<script id="feedbackDisplay">window.location.href='http://maliciouswebsite.com';</script>

Lorsque le formulaire tentera d’afficher le commentaire suivant, il exécutera plutôt le script, qui redirigera l’utilisateur vers un site web malveillant.

Cette situation crée une vulnérabilité, car l’application utilise les données fournies par l’utilisateur pour générer du HTML, et crée ainsi une balise script portant le même ID qu’un élément existant. Il s’agit d’un cas de DOM clobbering aux conséquences graves : l’attaquant peut prendre le contrôle du navigateur de l’utilisateur et ainsi voler des données sensibles ou installer un logiciel malveillant.

Bonnes pratiques de codage sécurisé pour prévenir le DOM clobbering

Maintenant que nous comprenons mieux ces vulnérabilités, passons à quelques bonnes pratiques pour réduire les risques de DOM clobbering.

Définir correctement la portée des variables et des fonctions

L’une des causes les plus fréquentes du DOM clobbering est une mauvaise utilisation de la portée globale en JavaScript. En définissant les variables et les fonctions dans une portée spécifique, nous limitons leur remplacement à cette portée ou à ses portées imbriquées, et réduisons les risques de conflit. 

Appliquons les règles de portée de JavaScript et refactorisons l’exemple précédent pour voir comment procéder :

(function() {
    // All variables and functions are now in this function's scope
    const form = document.querySelector('form');
    const feedbackElement = document.getElementById('feedbackDisplay');

    form.onsubmit = function(event) {
        event.preventDefault();

        const name = document.getElementById('name').value;
        const feedback = document.getElementById('feedback').value;

        // Sanitize user input
        name = DOMPurify.sanitize(name);
        feedback = DOMPurify.sanitize(feedback);

        const newFeedback = document.createElement('p');
        newFeedback.textContent = `${name}: ${feedback}`;
        feedbackElement.appendChild(newFeedback);
    };
})();

Remarque : nous avons utilisé DOMPurify pour assainir le HTML dans le bloc de code ci-dessus. Vous pouvez l’installer dans Node.js avec npm install dompurify. Ajoutez-le à votre HTML avec <script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/2.3.3/purify.min.js"></script>.

Dans cette version du code, nous avons placé l’ensemble dans une expression de fonction immédiatement invoquée (IIFE), qui crée une nouvelle portée. Les variables form et feedbackElement, ainsi que la fonction affectée au gestionnaire d’événement onsubmit, ne se trouvent pas dans la portée globale et ne peuvent donc pas être remplacées par le DOM.

Utiliser des identifiants uniques

Attribuer un ID unique à chaque élément de notre page web réduit le risque de remplacer involontairement une fonction ou une variable importante. Évitez également les noms courants ou susceptibles d’entrer en conflit avec des objets ou des fonctions JavaScript globaux.

Éviter de polluer l’espace de noms global

Garder l’espace de noms global propre est essentiel pour écrire du code JavaScript sécurisé. Plus la portée globale contient de variables et de fonctions, plus le risque de DOM clobbering augmente. Utilisez la portée des fonctions JavaScript ou la portée des blocs introduite par ES6 pour contenir vos variables et vos fonctions. Voici un exemple avec cette dernière :

    // All variables and functions are now in this block's scope
    let form = document.querySelector('form');
    let feedbackElement = document.getElementById('feedbackDisplay');

    form.onsubmit = function(event) {
        event.preventDefault();

        let name = document.getElementById('name').value;
        let feedback = document.getElementById('feedback').value;

        // Sanitize user input
        name = DOMPurify.sanitize(name);
        feedback = DOMPurify.sanitize(feedback);

        let newFeedback = document.createElement('p');
        newFeedback.textContent = `${name}: ${feedback}`;
        feedbackElement.appendChild(newFeedback);
    };

Dans ce code, nous avons utilisé un bloc (délimité par {}) pour créer une nouvelle portée. Toutes les variables et fonctions sont désormais limitées à ce bloc et ne se trouvent pas dans la portée globale.

Remarque : les exemples de code de ce guide sont simplifiés à des fins de démonstration et ne sont pas destinés à être utilisés sur un site web ou dans une application en production.

Utiliser les fonctionnalités JavaScript pour atténuer le DOM clobbering

JavaScript moderne propose plusieurs fonctionnalités qui contribuent à réduire le risque de DOM clobbering. En particulier, les mots-clés let et const, introduits avec ES6, offrent un meilleur contrôle sur la déclaration des variables.

Traditionnellement, nous déclarions les variables JavaScript avec le mot-clé var. Cependant, var présente quelques particularités, notamment l’absence de portée de bloc : il ne dispose que d’une portée de fonction et d’une portée globale. Ainsi, une variable déclarée avec var peut être accessible et remplacée en dehors du bloc dans lequel nous l’avons déclarée.

En revanche, let et const ont tous deux une portée de bloc : ils ne sont accessibles que dans le bloc où ils ont été déclarés. Cette caractéristique en fait souvent de meilleurs choix pour déclarer des variables, car elle limite les risques de remplacement.

Nous pouvons également utiliser const pour déclarer des constantes, c’est-à-dire des valeurs qui ne peuvent plus être modifiées après leur affectation. Elles constituent un autre moyen utile d’éviter les changements involontaires de variables importantes.

Voyons comment mettre en œuvre ces fonctionnalités dans le code de notre formulaire de commentaires pour prévenir le DOM clobbering :

    const form = document.querySelector('form');
    const feedbackElement = document.getElementById('feedbackDisplay');

    form.onsubmit = function(event) {
        event.preventDefault();

        const name = document.getElementById('name').value;
        const feedback = document.getElementById('feedback').value;

        // Sanitize user input
        name = DOMPurify.sanitize(name);
        feedback = DOMPurify.sanitize(feedback);

        const newFeedback = document.createElement('p');
        newFeedback.textContent = `${name}: ${feedback}`;
        feedbackElement.appendChild(newFeedback);
    };

Dans ce code, nous avons remplacé toutes les occurrences de var par const. Toutes les variables sont limitées au bloc dans lequel elles ont été déclarées et les constantes ne peuvent pas être remplacées.

N’oubliez pas que l’utilisation de let et de const n’élimine pas le risque de DOM clobbering, mais reste un aspect essentiel du codage sécurisé. Comprendre et utiliser ces fonctionnalités JavaScript peut renforcer la sécurité de vos applications web.

Surveiller et détecter les vulnérabilités liées au DOM clobbering

Les pratiques de codage sécurisé peuvent aider à prévenir le DOM clobbering, mais il est tout aussi essentiel de détecter les vulnérabilités lorsqu’elles surviennent. Découvrons quelques outils et techniques qui peuvent vous aider.

Analyse statique du code

Les outils de test de sécurité des applications statique (SAST), comme Snyk Code, peuvent détecter efficacement les vulnérabilités potentielles, notamment le DOM clobbering. Ils analysent votre code source sans l’exécuter et repèrent les schémas susceptibles de provoquer des problèmes de sécurité. Grâce à sa couverture complète de JavaScript, Snyk est un excellent choix pour les développeurs web.

Outils de développement du navigateur

Les outils de développement des navigateurs, comme ceux de Chrome ou Firefox, peuvent être de précieux alliés pour détecter les vulnérabilités liées au DOM clobbering. 

Voici un bref exemple d’utilisation de ces outils pour repérer un problème potentiel :

  1. Sur votre page web, faites un clic droit n’importe où et sélectionnez Inspecter pour ouvrir les outils de développement. Vous pouvez aussi utiliser Ctrl+Maj+I sous Windows ou Cmd+Option+I sous macOS.

  2. Ouvrez l’onglet Console, où s’affichent les erreurs et les journaux JavaScript.

  3. Saisissez « window » dans la console, puis appuyez sur Entrée pour examiner la portée globale et toutes les variables et fonctions globales. Repérez les variables qui semblent déplacées, en particulier celles qui portent le même nom que des ID ou des attributs name de vos éléments HTML.

  4. Dans l’onglet Éléments, modifiez le HTML de la page pour manipuler le DOM et tester les vulnérabilités potentielles. Par exemple, ajoutez un élément dont l’ID correspond à celui d’une variable ou d’une fonction globale pour voir si cette dernière est remplacée.

Conclusion

Cet article a expliqué comment se produit le DOM clobbering, les risques associés et les moyens de s’en prémunir. Définir correctement la portée des variables, utiliser des identifiants uniques et choisir les fonctionnalités JavaScript let et const de manière appropriée sont des mesures essentielles pour réduire cette vulnérabilité. 

Pour garantir la sécurité du code, il faut équilibrer les efforts de prévention et de détection. En appliquant les bonnes pratiques de codage et en utilisant les outils de développement des navigateurs ainsi qu’une solution SAST comme Snyk, vous pouvez repérer les problèmes existants et potentiels. 

En intégrant ces techniques à vos applications JavaScript et en vous tenant au courant des tendances en matière de sécurité des applications web, vous pouvez adopter une approche proactive pour sécuriser vos pratiques de codage.

Snyk pour sécuriser JavaScript

De votre première ligne de code à votre dernière dépendance npm, Snyk sécurise vos applications JavaScript directement depuis votre IDE, votre CLI et vos workflows Git.

Publié dans:

Lire la suite

feature insights context
Blog

Les attaques autonomes sont déjà là. La défense doit suivre leur rythme.

Les attaquants autonomes réduisent la fenêtre de défense. Découvrez comment la découverte, la correction, la validation et la prévention continues peuvent aider les équipes de sécurité à suivre le rythme.

illustration hero ai
Blog

L’ouragan de l’IA est là

L’IA accélère à la fois la création de logiciels et les cyberattaques. Les dirigeants doivent sécuriser les agents et le code dès leur conception, appliquer des contrôles à l’exécution et valider les défenses de manière indépendante.

feature insights context
Blog

La prévention est-elle essentiellement un problème résolu ?

La prévention dans le code généré par les agents est résolue sur le plan architectural, mais le défi reste de choisir des contrôles qui protègent la sécurité sans ralentir le développement.