Mapeo de la confusión de dependencias: un nuevo enfoque de detección mediante archivos source map
El artículo analiza las vulnerabilidades de confusión de dependencias, presenta una técnica novedosa de detección y explotación, y ofrece pasos prácticos para mitigar los riesgos asociados a esta vulnerabilidad.
Introducción
La cadena de suministro de software abarca todos los elementos que intervienen en el ciclo de vida de un código, desde el desarrollo hasta el despliegue, incluidos el código, los binarios y las dependencias procedentes de repositorios o gestores de paquetes.
Aunque utilizar código de terceros puede ahorrar tiempo y esfuerzo, también introduce posibles riesgos de seguridad, ya que los fallos de estas dependencias pueden comprometer la seguridad de toda la cadena de suministro.
Algunas empresas optan por utilizar dependencias internas en lugar de código de terceros, alojadas en registros privados o públicos con ámbitos privados, pero este enfoque no está exento de problemas de seguridad y puede dar lugar a la confusión de dependencias, un nuevo patrón de vulnerabilidad en la cadena de suministro de software.
La confusión de dependencias puede darse en muchos gestores de paquetes distintos. En este artículo nos centraremos en npm, un gestor de paquetes muy utilizado para Javascript.
Descripción general de la confusión de dependencias
La confusión de dependencias se produce cuando un actor malicioso consigue engañar a un gestor de paquetes (NPM, PyPI, RubyGems, JFrog Artifactory...) para que descargue un paquete o una dependencia maliciosos en lugar de uno legítimo. Esto puede lograrse mediante varias configuraciones incorrectas y suposiciones erróneas, entre ellas:
Errores tipográficos
Una de las formas más sencillas en que puede producirse la confusión de dependencias es mediante un error tipográfico: el atacante prevé que los desarrolladores puedan instalar por descuido el paquete malicioso en lugar del deseado debido a errores de escritura o a un despiste, normalmente publicando un paquete malicioso similar a uno popular, pero con un error tipográfico o una pequeña variación en el nombre.
Prioridad de los paquetes
Los desarrolladores pueden asumir que el gestor de paquetes siempre dará prioridad a los paquetes internos sobre los públicos, y por ello basarse en nombres de paquetes que existen internamente pero no en los registros públicos. Esto puede funcionar bien hasta que un actor malicioso descubre ese nombre de paquete y decide reclamarlo en el registro público.
Este es un ejemplo de archivo package.json de un proyecto javascript
{
"name": "my-project",
"version": "1.0.0",
"description": "Project 1.0.0",
"main": "index.js",
"scripts": {
"start": "node index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Corp",
"license": "MIT",
"dependencies": {
"express": "^4.17.1",
"lodash": "^4.17.21",
"axios": "^0.25.0",
"moment": "^2.29.1",
"internal-corp-package": "^1.5.0"
},
"devDependencies": {
"nodemon": "^2.0.15",
"eslint": "^8.6.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.25.1"
}
}
Una dependencia destaca: internal-corp-package. Su nombre sugiere que procede de un registro interno o que está destinada a un uso interno. Podemos comprobar si existe en el registro público.

Esto significa que el paquete solo existe en un registro interno. Supongamos que un actor malicioso descubrió este paquete interno y decidió publicar un paquete malicioso con el mismo nombre en el registro público de npm; tendríamos el siguiente flujo:

A continuación se explica en detalle la figura anterior:
- El registro no está configurado explícitamente
Esto significa que el usuario no indicó explícitamente a npm que utilizara el registro interno, ni ejecutando npm config set registry http://[internal registry] ni especificándolo en el archivo .npmrc. En tal caso, npm usará por defecto el registro público de npm https://registry.npmjs.org/ para la instalación, lo que significa que instalará el paquete malicioso publicado por el atacante.
- El registro está configurado correctamente
Aquí tenemos los dos escenarios siguientes:
- La versión del paquete especificada es estricta
Si la versión del paquete especificada es estricta, como 1.0.0, y esa versión existe en el registro privado, npm la usará automáticamente por defecto, lo que significa que instalará el paquete legítimo. Un ejemplo de package.json en el que la versión es estricta es el siguiente:
{
...
"dependencies": {
...
"internal-corp-package": "1.0.0"
},
...
}
- La versión del paquete especificada es laxa (rango)
Además de una versión estricta, npm permite expresar las versiones de tres formas distintas:
^1.0.0: npm instalará la versión 1.0.0 o cualquier versión superior a 1.0.0 e inferior a 2.0.0, como 1.9.5 (puede actualizar la subversión y el nivel de parche, pero no la versión principal).~1.0.0: significa que npm instalará la versión 1.0.0 o cualquier versión igual o superior a 1.0.0 e inferior a 1.1.0, como 1.0.9 (solo puede actualizar el nivel de parche).*: significa que no hay ninguna restricción sobre la versión instalada; en tal caso npm usará por defecto la última versión.
En todos los casos anteriores, un atacante puede llevar a cabo igualmente un ataque de confusión de dependencias, siempre que consiga dar con la versión adecuada. Por ejemplo, si la versión especificada es ^1.0.0, un atacante puede crear y publicar un paquete malicioso con la versión 1.0.1 y superará la comprobación.
Impacto de la confusión de dependencias
La confusión de dependencias puede escalarse a ejecución remota de código con bastante facilidad. El atacante dispone de muchas formas de ejecutar código arbitrario a través del paquete malicioso. Una de las más sencillas es la propiedad scripts de package.json, donde el atacante puede especificar comandos que se ejecuten en cualquier fase de la instalación, por ejemplo:
{
"name": "internal-corp-package",
"version": "1.0.1",
"description": "Definitely not a malicious package!",
"main": "index.js",
"scripts": {
"postinstall": "ping [malicious host]",
},
"author": "",
"license": "ISC"
}
Nombre de organización no reclamado
Los registros de NPM tienen una funcionalidad para usar un ámbito de organización en el que el nombre del paquete tiene el formato @org/package. Algunas organizaciones pueden registrar el nombre de su organización en el registro privado, pero no en el registro público de npm. Si un actor malicioso consigue reclamar ese nombre de organización en el registro público de npm, puede utilizarlo para llevar a cabo un ataque de confusión de dependencias.
Explotación de la confusión de dependencias mediante archivos source map
Los archivos source map, a menudo denominados simplemente source maps, son archivos que proporcionan una correspondencia entre el código fuente de un programa y el código transformado que ejecuta el navegador u otro motor de JavaScript. Se utilizan habitualmente en el desarrollo web para facilitar la depuración y la notificación de errores, especialmente cuando se trabaja con código minificado o transpilado.
Aunque los source maps son útiles para el desarrollo y la depuración, pueden tener implicaciones de seguridad, sobre todo cuando se exponen por descuido en compilaciones de producción. Si un archivo source map se deja expuesto públicamente en una compilación de producción, puede permitir a un atacante reconstruir el código fuente original sin minificar y enumerar sus dependencias, que normalmente se encuentran en la carpeta node_modules; tener acceso a la lista de dependencias puede ser fundamental para la confusión de dependencias.
Un archivo source map típico generado por webpack tiene este aspecto:
{
"version": 3,
"file": "bundle.js.map",
"sources": [
"webpack:///./src/file1.ts",
"webpack:///./src/file2.ts",
"webpack:///./node_modules/internal-corp-package/utils.js"
],
"sourcesContent": [
"console.log('This is file1');",
"console.log('This is file2');",
"console.log('This is internal package utils file');"
],
"mappings": "AAAA,OAAO,EAAE;AACP,OAAO,CAAC,GAAG,EAAE",
"sourceRoot": ""
}
Este source map nos permite recuperar el código original sin minificar de src/file.ts, src/file2.ts y node_modules/internal-corp-package/utils.js.
En algunos casos fue posible encontrar dependencias incrustadas directamente en el código fuente; tener acceso a los nombres de esas dependencias también podría ser fundamental para la confusión de dependencias.
Lo que resulta aún más preocupante de esta técnica es que, como el atacante tiene ahora acceso al código original de las dependencias anteriores, puede inyectar en el código una puerta trasera sigilosa sin romperlo, lo que le permite ejecutar código malicioso en el entorno objetivo manteniéndolo operativo. Esto hace que sea aún más difícil de detectar, ya que las aplicaciones que dependen de este paquete vulnerable seguirán funcionando sin señal alguna de compromiso, a diferencia del campo scripts mencionado anteriormente, que puede detectarse con facilidad.
Además del riesgo de confusión de dependencias, cuando un atacante recupera el código fuente original de aplicaciones javascript internas, este código puede contener credenciales incrustadas que, de otro modo, son difíciles de detectar en código minificado, o comentarios que normalmente se eliminan en el código transpilado.
Escaneo de programas de bug bounty
Durante nuestro análisis ejecutamos un escaneo sobre 191 activos de distintos programas de bug bounty, y aproximadamente el 5% de ellos resultó vulnerable a la confusión de dependencias mediante esta técnica.

Cómo protegerse frente a los ataques de confusión de dependencias
Aunque no existe una solución mágica contra la confusión de dependencias, hay medidas que se pueden tomar para mitigar el riesgo hasta cierto punto, entre ellas:
- Utilizar un ámbito de organización
Los registros de NPM ofrecen la opción de registrar un nombre de organización y utilizarlo como ámbito. La organización puede usarse en su registro interno, pero también tendrá que registrarla en el registro público de npm. De este modo, un atacante no podrá llevar a cabo la confusión de dependencias sin tener acceso a su organización.
- Registrar los nombres de los paquetes internos en el registro público de npm
Una forma de adelantarse proactivamente a los atacantes es reclamar en el registro público de npm los nombres de los paquetes que se utilizan internamente. Sin embargo, este enfoque no es infalible, ya que podrían pasarse por alto algunos paquetes.
- Validar la integridad de los paquetes
Cada versión de un paquete de npm tiene un hash sha512. Puede utilizar ese hash para verificar la integridad del paquete antes de descargarlo.
- Fijación de versiones
En lugar de usar versiones laxas como ^1.0.0, puede utilizar la versión exacta 1.0.0; de este modo, NPM no pasará por defecto a una versión superior. La salvedad es que la fijación de versiones no se aplica a las dependencias transitivas.
- Bloqueo de paquetes
Utilice archivos package-lock.json o yarn.lock para bloquear las versiones concretas de las dependencias empleadas en su proyecto. Esto evita cambios inesperados en las versiones de las dependencias durante las instalaciones, y bloquea tanto las dependencias directas como las transitivas.
Conclusión
En conclusión, la confusión de dependencias se ha convertido en una preocupación acuciante que puede comprometer la seguridad de cadenas de suministro de software enteras, por lo que adoptar medidas para protegerse frente a ella ha cobrado una importancia primordial.
Etiquetas:
supply chain