Javascript流行已久,V8引擎更是让其进化为服务端开发语言的一员。其对应的包管理工具NPM中存在着大量的可重用软件包,体量巨大。社区的热度促进了NPM的发展,也促进了Node.js的发展。但在NPM中发布一个新的包只需要注册一个NPM账户即可,更新一个包的新版本也只需要简单地进行注册表上传同步。恶意包在近几年成为一大焦点问题,因为哪怕官方很快地发现并清除了这些恶意包,在注册表中存活很短一段时间最终也会影响到一大片终端用户。
要从根本上解决这个问题,无非是从两个方面:一是阻断这些恶意包被同步到注册表中,即严格执行审查机制,在这些恶意包从上传开始到实际被同步到注册表中的这段时间里进行检查并清理;二是执行访问控制来限制包的安装过程,缩小攻击面。作者则是考虑在不改变现有的NPM基本过程的情况下,尽可能快地检测出恶意包,然后清理。(题外话,读者认为如此重要的一大社区生态,为了提高整体安全性做出一些改变是值得好好考虑的。
作者为了验证其方法在实际情况下的有效性,提出了三个准则:
作者提出了AMALFI来满足上述有效性准则:
单包特征包括:
多版本特征包括:
需要说明的是,对于第一次上传到注册表中的包,作者引入了一个伪更新版本,设定版本发布之间的时间为0,更新类型则是单包特征中的描述。(题外话,作者并没有解释为何设定为0,读者存疑:难道不存在一个年久失修的包突然被攻击者接管的可能性么?
作者使用Tree-sitter来解析包中的Javascript和Typescript文件以获取单包特征:对于特征1,字符串匹配;对于特征2和特征3,API匹配;对于特征4,调用eval和Function来生成代码;对于特征5,考虑Shannon熵值。对于多版本特征则是利用npm view time获取版本发布时间间隔;利用语义分析获取更新类型。(作者未在文中给出相关启发式规则,感兴趣可以进一步去代码中看。
据读者先前的研究,NPM删除的包是不存在缓存的,也就是说没有任何途径再从官方的源中下载到。作者的恶意包是找NPM索取的。在索要到的643个恶意包的基础上,作者增加了1147个同一日期发布的正常包,构成了一个总量为1790的带标签语料库(题外话,这个语料库的量对于在当时单版本就有170w个包的总数据量是否有些小了,但在这个领域样本差距也是无可奈何的