Detecta y evita ataques de confusión de dependencias en npm para mantener la seguridad de la cadena de suministro
13 de septiembre de 2021
0 minutos de lecturaEl 9 de febrero de 2021, Alex Birsan dio a conocer su investigación de seguridad, acertadamente llamada confusión de dependencias. En su divulgación, describe cómo un nuevo ataque a la cadena de suministro, que aprovecha errores de configuración de los desarrolladores y fallas de diseño de numerosos administradores de paquetes en ecosistemas de software de código abierto basados en lenguajes de programación, le permitió obtener acceso y exfiltrar datos de empresas como Yelp, Tesla, Apple, Microsoft y otras.
Esta investigación de seguridad despertó interés y, con ello, dio lugar a una nueva variedad de herramientas para ayudar a las organizaciones a detectar si son vulnerables o están expuestas a posibles ataques de confusión de dependencias.
En este artículo, te explicaré paso a paso en qué consiste el ataque de confusión de dependencias y cómo afecta a los desarrolladores de JavaScript y Node.js que trabajan en el ecosistema npm. También veremos una nueva herramienta de código abierto de Snyk que te permite detectar posibles riesgos de confusión de dependencias en tus propios repositorios de código fuente: snync.
¡Pregunta rápida! ¿Adivinas qué significa snync? La respuesta está al final...
En este artículo, conoceremos las distintas formas en que se manifiesta este ataque a la cadena de suministro y detallaremos cómo mitigar cada una:
Error de configuración del registro npm privado
El registro npm privado obtiene las versiones más recientes
Las actualizaciones manuales de paquetes pueden introducir versiones maliciosas
¿Te afectan los ataques de confusión de dependencias?
El ataque de confusión de dependencias solo afecta a las organizaciones que dependen de bibliotecas de código fuente internas. Es muy común administrar paquetes privados para proteger la propiedad intelectual, por lo que muchas organizaciones utilizan proxies internos, cachés o servicios de alojamiento de paquetes privados para hacerlo.
Si una organización administra un paquete privado interno, por definición, este no estará disponible en los registros públicos ni en sus réplicas. Como el paquete privado no figura en un registro público, cualquier otra persona puede reservar ese nombre y potencialmente lanzar un ataque de confusión de dependencias contra ti. El hecho de que un paquete privado pueda tener el mismo nombre que uno público es la base de este ataque.
En los ecosistemas de JavaScript y Node.js, la superficie de ataque de la confusión de dependencias se reduce considerablemente si usas paquetes con ámbito como espacio de nombres reservado. Pero ten en cuenta que, tanto si usas npm como yarn, estás expuesto a este ataque a la cadena de suministro.
Cómo reproducir ataques de confusión de dependencias
Para explorar de forma práctica los ataques a la cadena de suministro que toman la forma de confusión de dependencias, haremos un tutorial práctico que demostrará la vulnerabilidad y cómo mitigarla.
A continuación se muestra el manifiesto de paquetes de un proyecto conocido internamente como la Estrella de la Muerte. ¡Un gran nombre, lo sé!
Este código se encuentra en package.json y también muestra las dependencias del proyecto. Como puedes ver, superlaser es uno de los paquetes de los que depende. Por supuesto, se trata de un arma muy secreta que posee el imperio, por lo que solo se publica de forma interna. La Estrella de la Muerte debe ser móvil en el espacio y, por ello, también depende del paquete privado death-star-secret-hyper-matter-reactor.
Cómo configurar un registro npm privado
Si quieres seguir los pasos desde casa, haremos una breve pausa en el tema principal del artículo para configurar un registro npm privado. Con un registro privado, puedes hacer pruebas por tu cuenta y reproducir de principio a fin este ataque de confusión de dependencias en la cadena de suministro.
Para hacerlo, configuraremos un registro npm privado interno y un servidor proxy con Verdaccio, un proyecto de código abierto creado para este fin. Si tienes Docker instalado, podemos ponerlo en marcha fácilmente así:
Si todo sale bien, Verdaccio nos dará la bienvenida:

¡Felicitaciones! Ahora tienes tu propio alojamiento dedicado de paquetes npm ejecutándose localmente en el puerto 4873.
Publiquemos nuestros paquetes npm internos y secretos superlaser y death-star-secret-hyper-matter-reactor. Primero, agregaremos un usuario a este nuevo registro privado de Verdaccio:
A continuación, veremos el archivo de manifiesto del paquete superlaser, package.json:
El manifiesto del paquete death-star-secret-hyper-matter-reactor es similar, solo cambia el nombre del paquete. Publiquémoslo en nuestro registro npm privado:
¿Por qué existe la confusión de dependencias?
Hay principalmente tres situaciones que pueden dar lugar a este tipo de ataques a la cadena de suministro de software:
Error de configuración en un servidor de desarrollo o de pruebas
Publicación de versiones más recientes de paquetes en el registro npm público
Posibles fallas de diseño en los administradores de paquetes
Veamos de nuevo cada una de estas situaciones.
Error de configuración del registro npm privado
Cuando un desarrollador o un sistema de integración continua (CI) clona el código fuente del the-death-star project, que tiene la dependencia interna superlaser, ¿cómo obtiene esta dependencia?
Al ejecutar el comando npm install, probablemente deba cumplir estos requisitos:
Necesita la URL del registro npm privado donde se encuentra este paquete interno.
Necesita un token o algún tipo de credenciales para acceder a ese registro privado.
El primer paso mencionado es donde algo puede salir mal. Para especificar un registro npm privado en particular, es necesario proporcionar explícitamente la información de configuración del administrador de paquetes npm.
Veamos ahora algunas situaciones:
¿Qué pasa si el sistema de integración continua no tiene configurado el registro privado?
¿Qué pasa si eres un nuevo desarrollador que se incorpora a un proyecto existente y no realizaste pasos previos, como ejecutar el comando
npm config set registry?¿Qué pasa si por error eliminaste o modificaste tu configuración
.npmrcy ya no incluye el registro npm privado interno?
En cualquiera de estos casos, si se omite la configuración personalizada del registro interno, el administrador de paquetes npm usará de forma predeterminada el registro público (registry.npmjs.org) y descargará paquetes de ahí.
Cualquiera puede publicar paquetes en el registro npm público. Por eso, si un usuario malicioso publicara un paquete llamado superlaser, este se descargaría e instalaría en lugar de tu propio paquete interno.
Cómo protegerte contra la confusión de dependencias en npm
El problema central es no tener configurado correctamente el proxy npm privado. Si un desarrollador o un sistema de CI no cuenta con esta configuración, podrías estar expuesto.
Por eso, el primer paso es: Asegúrate siempre de que haya un archivo .npmrc disponible o de contar con otro tipo de configuración para el proxy npm privado.
En segundo lugar, puedes adoptar un enfoque proactivo para detectar casos en los que usas paquetes privados cuyo espacio de nombres no está reservado en el registro npmjs público. Creamos snync para ayudarte con eso. Puedes ejecutarlo en un servidor de CI como parte de los pasos, antes de instalar activamente las dependencias. Así, puede evitar que instales por error un paquete malicioso.
En la siguiente captura de pantalla, ejecuto snync mediante npx e indico el directorio actual para analizar las dependencias. También especifico que el paquete llamado superlaser es efectivamente un paquete privado:

Como puedes ver en los resultados, snync confirmó dos posibles problemas específicos relacionados con la confusión de dependencias:
El paquete
death-star-secret-hyper-matter-reactoresvulnerableporque actualmente no hay ningún paquete con ese nombre registrado en el registro npmjs público. Esto significa que cualquiera puede registrarlo y provocar un ataque de confusión de dependencias.El paquete
superlaseressuspicious. Esto significa que la herramienta detectó uno de estos dos casos:Este nombre de paquete se introdujo primero en el código fuente de Git y, más adelante, se publicó en el registro npmjs público un paquete con el mismo nombre. Esto no significa que el paquete público en el registro npmjs sea malicioso, pero sí amerita una revisión.
El nombre del paquete ya existe en el registro npmjs público, incluso antes de que crearas un paquete privado con el mismo nombre.
snync es un proyecto de línea de comandos de código abierto basado en Node.js. Te invitamos a usarlo en tu flujo de seguridad DevSecOps.
El registro npm privado obtiene las versiones más recientes
¿Qué pasa si hay un paquete con el mismo nombre que el nuestro (superlaser) publicado y disponible en el registro npm público, pero con una versión semver superior?
La situación es la siguiente:
superlaser@1.0.0está disponible en el registro npm privado https://localhost:4783Un usuario anónimo publicó
superlaser@1.99.999en el registro npm público, en https://www.npmjs.com/package/superlaser
Ahora bien, ¿qué pasa si se crea un proyecto nuevo y se solicita instalar el paquete superlaser? Todavía no hay ningún package.json ni archivo de bloqueo (package-lock.json). El desarrollador simplemente empieza con:
Esto podría terminar instalando una versión maliciosa de superlaser controlada por un atacante remoto. Pero ¿por qué? El desarrollador tiene configurado el registro npm local.
Las pruebas demuestran que, incluso cuando está configurado un proxy npm privado interno, muchos de estos proxies primero comprueban cuál es la versión más reciente disponible en el registro npm público. Si existe una versión más nueva, obtienen del registro público la versión semver más reciente del paquete y la instalan.
Reproduzcamos esta situación con Verdaccio. Como puedes ver a continuación, subí el inofensivo paquete npm superlaser a Verdaccio, que me sirve para alojar internamente paquetes npm privados:

A continuación, te mostraré cómo, en el directorio de un proyecto nuevo que solo tiene el archivo .npmrc apuntando al registro local de Verdaccio, el comando npm install para el paquete superlaser obtiene la versión más reciente del registro npm público, aunque esperaba recibir solo superlaser@1.0.0, que es la versión que publiqué internamente:

Esto produce un resultado inesperado y puede poner en riesgo a los usuarios finales.
Si quieres participar en la conversación y seguir este tema, hay un debate público sobre este comportamiento en el proyecto de código abierto Verdaccio en GitHub.
Técnicamente, este proceso que usan Verdaccio y otros proxies npm privados toma en cuenta varias variables, como:
La versión semver más reciente
La fecha de publicación del paquete
Por ejemplo, si existe una versión semver alta en el registro npmjs público, pero se crea un paquete con el mismo nombre y una versión semver inferior (dentro del mismo rango que la versión pública) después de la fecha de publicación de la versión superior, Verdaccio no obtendrá el paquete del registro público.
Cómo evitar obtener el paquete equivocado
Configura tu proxy npm privado para que nunca envíe solicitudes al registro público. Si un paquete o una versión no está disponible localmente, debe resolverse de una forma que no obtenga paquetes a ciegas de fuentes no confiables ni verificadas.
Si usas Verdaccio, como en estos ejemplos, puedes configurarlo de la siguiente manera en /verdaccio/conf/config.yaml:
Lo anterior es el archivo de configuración estándar de Verdaccio para la versión del contenedor de Docker. Sin embargo, puedes ubicar el comentario # DO NOT FETCH PACKAGES FROM NPMJS, que comenta en la línea siguiente la opción proxy: npmjs. Esto impide que Verdaccio obtenga nada de npmjs para los paquetes que coincidan con el patrón indicado.
Las actualizaciones manuales de paquetes pueden introducir versiones maliciosas
En este escenario, actualizas manualmente tus paquetes de npm ejecutando npm update o npm install <packages>@latest para actualizar las versiones de tus dependencias.
Al ejecutar estos procedimientos de actualización, ocurre aquí el mismo comportamiento que vimos antes. El comando npm update le pide al proxy privado de npm que obtenga la versión más reciente y, a su vez, el proxy consulta cuál es la versión más actualizada en el registro público de npm.
Ten en cuenta que, si usas yarn, ejecutar yarn upgrade dará el mismo resultado: descargará paquetes potencialmente maliciosos del registro público npmjs.
Podemos demostrarlo con el siguiente escenario, en el que empezamos con la versión interna superlaser@1.0.0:
Ahora, tengo un archivo .npmrc que define el registro local y apunta al servidor de Verdaccio que tengo en ejecución. Sin embargo, si simplemente ejecuto npm update para actualizar todas mis dependencias, puedes ver que descarga la versión más reciente que coincide con semver desde el registro público npmjs:

¿Cómo protegerte?
En lugar de actualizar manualmente los paquetes de npm a ciegas, opta por las actualizaciones automatizadas de paquetes mediante pull requests dirigidos a los repositorios de tus proyectos de código abierto, que también se encargarán de sincronizar el manifiesto del paquete (como package-lock.json o yarn.lock).
Snyk es una opción gratuita para automatizar las actualizaciones de paquetes de npm, como muestra la siguiente captura de pantalla de un pull request combinado:

También puedes ajustar las opciones de actualización automática, por ejemplo, limitar la cantidad de pull requests que abrirá Snyk o ignorar por completo la actualización de paquetes específicos. Encontrarás más información en la documentación de Snyk sobre cómo actualizar dependencias con PR automáticos.
Empieza con los desafíos de Capture the Flag
Aprende a resolver desafíos de captura la bandera con nuestro taller virtual introductorio a pedido.
Para concluir: recursos sobre seguridad de aplicaciones
Si llegaste hasta aquí, te ganaste la respuesta al cuestionario que presentamos al inicio del artículo. Llamamos a la herramienta de código abierto snync como abreviatura de So Now You’re Not Confused. ¿Lo entendiste?
Hoy, ante los incidentes de seguridad de la cadena de suministro, es fundamental aplicar metodologías seguras, tanto al escribir código como en las prácticas diarias de seguridad para desarrolladores. Si tú o tu equipo trabajan habitualmente con JavaScript o Node.js, estos recursos de referencia les serán útiles: