Paquetes maliciosos de Node.js visiblemente invisibles: cuando las configuraciones poco conocidas se combinan con caracteres invisibles
Aviad Hahami
28 de febrero de 2022
0 minutos de lecturaEn los últimos años, hemos visto un enorme aumento en la cantidad de paquetes de código abierto que se crean y usan. Hoy en día, cada ecosistema tiene su gestor de paquetes, y casi todos los gestores de paquetes tienen sus joyas ocultas y configuraciones poco conocidas.
Dicho esto, a medida que los desarrolladores instalan continuamente un número cada vez mayor de paquetes, los atacantes se interesan más en las superficies de ataque de esos paquetes. Así comienza la búsqueda de crear el paquete malicioso perfectamente oculto.
Esta publicación se centra en ataques que aprovechan el ecosistema de Node.js y los gestores de paquetes yarn y npm. Sin embargo, considérala un llamado a la acción para buscar problemas similares en otros ecosistemas, crear conciencia y mejorar la seguridad general de nuestra comunidad.
Dicho esto, ¡vamos al grano!
Parte 1: Configuraciones poco conocidas
Hace unos días, un colega investigador me habló de algunas opciones de configuración interesantes de npm que no son muy conocidas y pueden tener graves implicaciones de seguridad (más tarde encontramos otras similares en yarn). Estas opciones permiten indicar al gestor de paquetes qué archivo binario debe ejecutar dentro del directorio del proyecto. Sí: ¡qué archivo binario!
En cuanto a la configuración, estas opciones son yarnPath en el archivo .yarnrc, así como git, shell y script-shell en el archivo .npmrc, entre otras. Para conocer el contexto completo del vector de ataque que describimos a continuación, recomiendo leer la publicación de Rotem (de Cider Security) sobre este hallazgo.
Alguien podría preguntar: «¿Por qué se respetarían estos archivos que acabo de clonar o descargar?». La respuesta es que así funcionan npm y yarn. Como parte del flujo de gestión del proyecto del gestor de paquetes, se buscan archivos .rc de forma jerárquica: primero en el directorio actual del proyecto, luego en el directorio del usuario y, por último, en el directorio raíz (puedes encontrar más información en la documentación de configuración de NPM).
Con las opciones anteriores, podemos crear un paquete malicioso parcialmente oculto (en repositorios o archivos tar, ya que los archivos `.rc` no se descargan al ejecutar `npm install` o su equivalente) usando el siguiente archivo .yarnrc como configuración:
(Nota: Este ejemplo usa un archivo .yarnrc, pero un archivo .npmrc similar, con la opción de configuración adecuada, produciría el mismo resultado.)
Al hacerlo, cualquier ejecución de yarn install en la raíz del proyecto anterior ejecutará evil.sh. Ahora, solo tenemos que agregar el archivo malicioso.
Dos notas importantes:
Aunque incluyas
--ignore-scriptso una instrucción equivalente, lo anterior seguirá ocurriendo.Si ese paquete es una dependencia de tu proyecto, el archivo .yarnrc malicioso no se respetará, ya que está dentro de
node_modulesy no al mismo nivel. Así que no hay riesgo.
Parte 2: Ocultar código a plena vista
Otro tema de interés durante el último año ha sido el código fuente troyanizado (o el código malicioso que resulta de usar caracteres con valores Unicode distintos de los que parecen tener).
La idea es bastante sencilla: por ejemplo, algunos caracteres (que no son espacios, CR, LF, etc.) se muestran como espacios en blanco, pero siguen siendo cadenas válidas.
Para profundizar un poco más, el carácter aparentemente vacío « » (0x3164 en hexadecimal) se llama «HANGUL FILLER» y, como puedes (o no puedes) ver, no es un espacio y es una «letra» válida. Podemos aprovechar esto para ocultar lógica específica en fragmentos de código y pasar desapercibidos al introducir código malicioso.
La información anterior basta para nuestra demostración, así que no profundizaré más en el tema. Si quieres más información, contexto y ejemplos, te recomiendo leer lo siguiente:
Parte 3: Binarios ocultos + configuraciones ocultas = RCE
Con lo anterior en mente, los siguientes pasos del ataque son sencillos:
Crear un paquete que parezca inofensivo.
Crear un archivo malicioso (con un nombre que use un carácter oculto).
Crear un archivo .rc.
Configurar el parámetro para que apunte al nombre del binario con el carácter invisible.
Una vez completados estos pasos, estamos listos. Veamos este ataque desde el punto de vista de las víctimas:
Si un desarrollador encuentra un paquete así sin conocer el vector de ataque, probablemente no detectará las señales de alerta.
Quizás tenga algo de cautela y ejecute ls -l. Al hacerlo, verá lo siguiente:
Como el resultado no parece extraño, esta es la primera trampa. En este punto, la mayoría de las víctimas ejecutará yarn install.
Pero supongamos que nuestro objetivo es un desarrollador con experiencia que podría agregar a ls. la opción para «mostrar elementos invisibles». El resultado sería:
Ahora, el objetivo podría preguntarse: «Bueno, ¿qué hay en el archivo .rc?» (ignorando por completo el archivo invisible y silencioso) y ejecutaría cat. El resultado sería:
Esta es la segunda trampa.
La mayoría de los desarrolladores no sabe que yarn (o npm) podría ejecutar binarios personalizados. Ver algo como lo anterior sí es raro, pero no dice «evil.sh», ¿verdad?
La tercera trampa es bastante sencilla: el objetivo simplemente no ve el archivo. Aunque haya visto el archivo .rc y revise la especificación yarn-path (lo mismo existe en npm), podría suponer que la propiedad no apunta a nada y terminar ejecutando el gestor de paquetes.
En mi prueba de concepto, ejecutar yarn install produce lo siguiente:
Medidas de mitigación y reflexiones
Puedes mantenerte a salvo de la siguiente manera:
Nunca confíes en el código de terceros. Asegúrate siempre de saber qué clonaste o descargaste, y mantente alerta ante comportamientos y configuraciones extraños.
Ejecuta el código en un entorno aislado. Ejecuta siempre el código de terceros en un entorno aislado o en un contenedor. Así, aunque el paquete sea malicioso, probablemente no afectará tu sistema ni comprometerá tu equipo.
Nota: Ejecutar código en un entorno aislado tampoco garantiza una seguridad del 100 %, ya que existen vulnerabilidades del kernel, escapes del entorno aislado, etc. Sin embargo, usar un entorno así reduce considerablemente el riesgo y bloquea la mayoría de los ataques sencillos (como una shell inversa básica o algo equivalente).
Y así, mitigamos el ataque.
Lo fascinante de este vector de ataque es que es completamente visible y, a la vez, invisible.
Alguien podría decir: «Siempre ejecuto ls -la, reviso todos los archivos y conozco bien todas las opciones de configuración de los gestores de paquetes». De acuerdo, pero ¿es así? Y, para ampliar la pregunta: ¿esto también es cierto para todas las personas de tu organización?
Y, como con los filtros de Bloom, la respuesta es «no significa no; sí significa quizá».
Quiero terminar reiterando que esto es un llamado a la acción —no a la explotación— para que la comunidad tome conciencia de estos vectores de ataque. Como todos crean aplicaciones e instalan dependencias casi a ciegas, es esencial señalar las configuraciones sospechosas a los demás.
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.



