NPM项目已正式承认其存在一个长期的安全漏洞,该漏洞可能导致恶意的代码包可以在开发者系统上随意的运行任意代码,导致了第一个NPM创建的蠕虫。在一篇名为“npm无法限制恶意代码包的行为”的 漏洞说明VU319816 中,Sam Saccone描述了创建一个蠕虫需要的 步骤 以及怎么让它自动传播。尽管这是在2016年一月被报道出来的,然而从NPM仓库管理初始版本发布开始,这个问题就已经存在而且被广泛知晓。
这个问题的根源在于,NPM模块有 相关的脚本文件 ,它们可以在安装的时候被NPM运行。当模块从NPM上被下载后,它有足够多的机会可以运行其代码:
这些脚本使得NPM模块的发布更加简单并可以在使用之前进行内容的后期处理。举例来说,一些模块原本是由CoffeeScript或者TypeScript编写的,它们需要一个转化的步骤,或者是用ES6编写的,需要Babel转化后才能运行在目前的浏览器上。另外,JavaScript库一般都会进行压缩(使其更小),这往往也是需要自动化运行的一个步骤。因为JavaScript是解释性的,像make这样的工具不会被使用,所以npm脚本就用来干这些苦力活。
在模块被客户端下载和使用的时候,NPM错误地使用了这些代码,不光用来完成编译时间验证,还进行客户端运行。举例来说, left-pad 惨状(InfoQ昨日报道)在重新发布模块后得到解决;然而,如果像这样被广泛使用的模块(例如 true ,其功能只是打印true)有被影响的代码块,那么这样的代码运行在成千上万的机器上也有了可能。
这些代码满足了蠕虫的创建条件,然后这些蠕虫持续地影响其他的代码包并依次传播。NPM包开发者使用标准的证书把他们的代码包(当然是通过npm)发布到 NPM目录 。为了加快发布,他们有可能会登录到该目录然后一直保持登录状态,此后任何的代码包都可以发布到该目录。正因如此,一旦一个npm包开发者的机器被攻占,蠕虫可以扫描其他的代码包,然后重新发布它们(通过开发者现有的证书),与此同时,蠕虫已经被注入到这些刚被感染的包的脚本代码块中。
甚至,包管理其自己也执行JavaScript,这意味着仅仅是解决包的依赖就会导致任意代码的执行。这里有个例子可以最作为该概念的认证,一个代码包使用 变量作为其包名 ,被发现可以伪装成任何的包。参考真实的开源精神,该 概念认证 是MIT证书认证:
A="$1" echo '{ "name": "'"$A"'", "version": "2.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo /"Error: no test specified/" && exit 1" }, "author": "", "license": "ISC" }' > package.json npm publish
如果一个包开发者下载了此代码包然后运行了其脚本,那么之后该包会运行index.js,之后便可以在终端机器上运行任意的代码。该例子就展示了怎样运行sudo rm -rf /,显然这会在没有任何备份的情况下造成严重后果。
NPM目录允许任何人重新规划一个存在的代码包,只需要在他们的空间里用相同的命名发布一个新的版本,这使得该问题变得更加复杂。除非开发者特意的写死他们所依赖的代码包版本,否则很有可能自动更新到最近的版本并在之后遭受攻击。
到目前为止 NPM的回复 都相当的软弱,否认了其对扫描恶意软件的责任,并指出开发者使用那些模块受到注入攻击也是咎由自取。然而整个基础都被设置成允许所有人拥有自己的包并可以用任何JavaScript替换这些包;尽管这些脚本可以被排除在外(通过使用using npm install --ignore-scripts 或者npm config set ignore-scripts true),然而还是有机会使用 require('shelljs').exec('rm -rf /')替代JavaScript文件的内容,其运行时还是有着破坏性的影响。并且,让一个npm会话保持登录意味着任何应用,无论是基于NodeJS还是其他语言,都可以很容易的在用户不知晓的情况下以当前用户的名义发布脚本。
最近的这些事件表明,随着JavaScript的以及服务器端NPM使用的快速增长,其被赋予了和JavaScript语言本身一样的安全性的考虑。唯一让人感到惊讶的是,整个事件花费了这么长的时间才水落石出。
查看英文原文: NPM Worm Vulnerability Disclosed
感谢张龙对本文的审校。
给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ,@丁晓昀),微信(微信号: InfoQChina )关注我们。