Skip to main content

Comprender las vulnerabilidades de toma de control del sistema de archivos en el administrador de paquetes JavaScript npm

Escrito por
prioritize vulns header

7 de enero de 2020

0 minutos de lectura

El 11 de diciembre de 2019 se divulgó públicamente una vulnerabilidad de seguridad que afecta a los principales administradores de paquetes de JavaScript (npm, yarn y pnpm). Esta vulnerabilidad, descubierta por el investigador de seguridad Daniel Ruf, permite que actores maliciosos ejecuten distintas tácticas para sobrescribir archivos arbitrarios.

En este artículo:

¿Cómo funcionan los paquetes de línea de comandos de Node.js?

Los paquetes de npm suelen especificar una clave bin en el archivo de manifiesto del paquete, package.json. El cliente del administrador de paquetes —por ejemplo, npm u otras alternativas— usa esta clave bin para declarar ejecutables disponibles en el símbolo del sistema mediante la variable de entorno global $PATH.

Probablemente ya hayas visto esta funcionalidad si instalaste paquetes de línea de comandos de forma global, como Angular CLI mediante @angular/cli, typescript o Snyk CLI mediante snyk —, que luego están disponibles en cualquier ubicación de tu ruta, sin importar el directorio de un proyecto npm específico. Para entender cómo se usa la clave bin, usamos unpkg para ver cómo funciona el proyecto Angular CLI; para ello, consultamos package.json en el sitio de unpkg.

El archivo package.json del paquete @angular/cli resalta la clave bin, que asigna el comando ng a ./bin/ng.
Campo package.json de ng-cli que muestra el uso de la clave JSON bin para definir un ejecutable cuando npm instala este paquete (fuente: unpkg https://unpkg.com/browse/@angular/cli)

Como puedes ver arriba, en esta versión del paquete, el archivo de manifiesto package.json declara un archivo ejecutable llamado ng, que apunta a un archivo incluido en el paquete y disponible en ./bin/ng.

Este archivo JavaScript ./bin/ng se vuelve ejecutable para el usuario al agregar al inicio del archivo una instrucción Unix Shebang. Esta instrucción le indica al shell qué intérprete debe usar para evaluar el archivo. En las aplicaciones basadas en Node.js, el binario de Node.js está instalado en el sistema operativo del usuario.

En el caso de Angular CLI, el contenido del archivo ejecutable se muestra en la siguiente imagen:

Código fuente del ejecutable ng de Angular CLI que muestra una comprobación de la versión de Node.js y un mensaje de error para las versiones no compatibles.
(fuente: https://unpkg.com/browse/@angular/cli@6.0.4/package.json)

¿Cómo afecta esta vulnerabilidad de seguridad al cliente del administrador de paquetes npm?

Para entender las implicaciones de esta vulnerabilidad, debemos profundizar un poco en npm y, en especial, en los mecanismos que usa para poner a disposición del usuario los ejecutables instalados.

El cliente npm administra los “bins” declarados por los paquetes instalados globalmente en un directorio específico, que usa para mantener enlaces simbólicos entre los ejecutables de esos paquetes y los enlaces en dicho directorio.

Para revisar qué directorio se usa para guardar estos ejecutables, usamos el cliente npm de la siguiente manera:

$ npm bin --global
/Users/lirantal/.nvm/versions/node/v10.16.3/bin

Al revisar la variable de entorno $PATH disponible en tu shell, encontrarás una entrada que corresponde al directorio de tu sistema operativo. Así es posible acceder desde cualquier directorio del sistema operativo a los paquetes instalados globalmente que declaran bins.

Con esta información, es inevitable preguntarse si un paquete puede declarar un archivo ejecutable bin que proporcione otro paquete. Eso es precisamente lo que Daniel quería entender, y lo que lo llevó a descubrir vulnerabilidades de sobrescritura arbitraria de archivos en los principales administradores de paquetes: npm, yarn y pnpm.

¿Cómo se produce en la práctica esta vulnerabilidad de seguridad de npm?

En Snyk, reproducimos esta vulnerabilidad de escritura arbitraria de archivos y mostramos cómo se explota, gracias a las pruebas de concepto de Daniel que compartió con el equipo de seguridad de Snyk.

El primer paquete define un conjunto global de ejecutables de la siguiente manera:

{
  "name": "binary-replacer",
  "version": "1.0.0",
  "main": "index.js",
  "author": "Daniel Ruf <mac1@daniel-ruf.de>",
  "license": "MIT",
  "bin": {
    "curl-test": "./bin/index.js",
  }
}

El ejecutable curl-test simplemente imprime texto en la consola:

#!/usr/bin/env node
console.log('hi')

Si instalamos este paquete globalmente mediante npm install -g binary-replace y luego ejecutamos el comando curl-test desde el shell, debería imprimir hi en la pantalla.

Ahora imagina que tenemos otro paquete, binary-replacer-2, que también define un ejecutable y tiene exactamente la misma clave bin que el paquete anterior: curl-test

{
  "name": "binary-replacer-2",
  "version": "1.0.0",
  "main": "index.js",
  "author": "Daniel Ruf <mac1@daniel-ruf.de>",
  "license": "MIT",
  "bin": {
    "curl-test": "./bin/index.js",
  }
}

Cuando el usuario instala este paquete globalmente, npm sobrescribe el enlace simbólico anterior con el ejecutable de este paquete, que en esta demostración es inofensivo:

#!/usr/bin/env node
console.log('hi 2')

Pero ¿qué pasaría si un paquete malicioso apuntara a algunos de los paquetes globales más conocidos? Hay muchas posibilidades: desde el paquete now de Zeit hasta la CLI fkill de Sindre Sorhous.

Además, Daniel describió muchas variantes de esta vulnerabilidad en npm, yarn y pnpm. Por ejemplo, ¿qué pasaría si, en lugar de usar la clave curl-test, la reemplazáramos por la ubicación de un enlace relativo?

 "bin": {
    "../../aaa": "/bin/date"
  }

O incluso podríamos establecer la clave como una ruta absoluta a una herramienta de línea de comandos conocida que el usuario suele usar desde el símbolo del sistema, por ejemplo:

"bin": {
  "/usr/bin/date": "./fake-date"
}

Es admirable que pnpm, dirigido por su único desarrollador, Zoltan Kochan, en la mayoría de los casos actúe de forma sensata y solo sea vulnerable cuando se proporciona una ruta relativa. Aun así, sigue siendo un problema, por lo que también hay una ruta de actualización a versiones corregidas de npm.

¿Por qué es tan grave esta vulnerabilidad de seguridad de npm?

Esta vulnerabilidad es aún más grave porque la sobrescritura arbitraria de archivos también ocurre con paquetes instalados mediante la opción de línea de comandos —ignore-scripts de npm, que, cabría esperar, evitaría que los binarios maliciosos llevaran a cabo esta misma acción.

Además, aunque hablamos del caso de los paquetes instalados globalmente y sus ejecutables, esto también se aplica a las dependencias transitivas del proyecto. Cuando las dependencias se instalan con un npm install normal, un paquete puede sobrescribir arbitrariamente archivos dentro y fuera del directorio del proyecto. En algunos casos, esto puede inyectar malware, permitir un ataque que altera los archivos de bloqueo o envenenar el sistema de archivos de otras maneras.

¿Estoy en riesgo y qué debo hacer?

Estás en riesgo en dos casos:

  1. Instalas un paquete malicioso, ya sea porque te engañan para que lo hagas o porque un atacante suplanta paquetes populares con nombres similares que terminas instalando.

  2. Un paquete no malicioso se ve comprometido de forma que se modifican las claves bin de package.json para sobrescribir archivos y también incluir un archivo malicioso que se ejecutará. Es muy poco probable, pero ya ha ocurrido dos veces: [1], [2]

El impacto de esta vulnerabilidad también provocó versiones de seguridad de Node.js, porque el cliente del administrador de paquetes npm viene incluido en Node.js y fue necesario actualizar npm a la versión corregida 6.13.4.

Daniel primero comunicó los hallazgos de la vulnerabilidad de seguridad al equipo de investigación de seguridad de Snyk para ayudar a coordinar la divulgación pública responsable entre todos los proyectos y responsables de mantenimiento de esos administradores de paquetes. Además, publicó más información sobre los tipos de ataques en su blog.

Si usas alguna de las siguientes versiones de npm, yarn o pnpm, estás en riesgo y es fundamental que las actualices de inmediato:

Aunque no es común incluir npm, yarn o pnpm como dependencias de un proyecto, si Snyk supervisa tu proyecto y detectamos versiones vulnerables de alguno de ellos, recibirás alertas periódicas de Snyk. Además, si conectaste tu repositorio de GitHub (o Bitbucket), creamos automáticamente un pull request para que actualices estos administradores de paquetes a sus versiones corregidas.

También te recomendamos adoptar prácticas de desarrollo seguro al trabajar con administradores de paquetes como npm. Para facilitarte la tarea, recopilamos convenciones de seguridad y prácticas recomendadas en 10 prácticas recomendadas de seguridad para npm.

¿Crees que encontraste una vulnerabilidad en un paquete de código abierto? En Snyk valoramos a la comunidad de seguridad y creemos que la divulgación responsable de vulnerabilidades en paquetes de código abierto nos ayuda a garantizar la seguridad y la privacidad de los usuarios. Si crees que encontraste una vulnerabilidad en un paquete de código abierto, puedes enviarla al programa de divulgación de vulnerabilidades de paquetes de Snyk Open Source y nos encargaremos del resto. https://snyk.io/vulnerability-disclosure/

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.

Leer más

blog feature snyk container custom base recommendations
Blog

Presentamos Evo Agentic AppSec: seguridad que sigue el ritmo del desarrollo con IA

El desarrollo impulsado por IA avanza más rápido que 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 establecen el nivel.

blog feature ai violet
Blog

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

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

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.