不可见的npm恶意软件——用精心制作的版本逃避安全检查

的npm CLI有一个非常方便和众所周知的安全特性——当安装一个npm包时,CLI会检查包及其所有依赖的众所周知的漏洞——

检查在包安装时触发(当运行时)npm安装),但也可以通过运行手动触发npm审计。
这是一个重要的安全措施,警告开发人员不要使用具有已知漏洞的包。
最近,我们遇到了npm工具的一个意想不到的行为,这可能会对安全产生影响npm安装和npm审计没有显示具有特定版本格式的包的建议,使使用这些包的开发人员面临潜在地将关键漏洞或恶意软件引入其系统和/或间接依赖于其NPM包的风险。
在这篇博文中,我们将进一步解释这个问题,攻击者如何利用它来逃避对其发布的恶意包的安全检查,并建议开发人员如何避免被它所迷惑。
意想不到的结果
在使用一些npm包时,我们注意到npm CLI和JFrog Xray报告的漏洞之间存在一个有趣的差异。
安装特定包时,即cruddl 2.0.0-update.2,我们从npm和Xray -收到了不同的漏洞指示


对于同一个包,npm发现0个漏洞,但Xray发现一个漏洞(CVE-2022-36084)。我们开始发现这是一个可能的bug,还是别的什么……
对问题进行分类
经过几次尝试和包安装后,我们意识到只有在安装的包版本包含破折号/连字符的字符(交货。1.2.3-a)。那么,为什么会这样呢?
当上传一个包时,NPM允许一个严格的版本格式语义版本控制并且必须被node-semver。以下是两个完全正确的版本:5.6.7,5.6.7-a。
npm-install/audit工具会将所有依赖包及其版本收集到一个json字典中,并将其发送到npm API端点批量咨询终端。端点遍历每个包和版本,尝试通过将版本与建议的影响范围相匹配来找到相关的建议。它将所有这些相关的通知附加到API端点返回的列表中。
我们已经确定,批量通知端点无法检索版本包含连字符(-)后跟附加字符的包的安全通知。
这些连字符的版本到底是什么?
根据语义版本控制规范,版本的格式为MAJOR.MINOR.PATCH。例如5.6.7。然而,如条款9预发布版本可以通过“在补丁版本后面立即添加连字符和一系列点分隔的标识符”来指定。如。5.6.7-a
猜测这种行为的来源
由于npm端点的代码是闭源的,我们只能猜测这个问题的根源。
每份建议(看例子)有一个影响版本保存逻辑表达式的字段(例如:> 6.6.0),以描述受影响版本的范围。如果某个版本满足任何逻辑表达式,则认为它是脆弱的。
根据node-semver的文档(以及可能的semver在比较具有预发布标签的版本时,有一些特殊的规则(请参阅文档)。
例如,默认情况下:
semver.满足("1.2.3-a", "> 0")
返回假,这可能是相当出乎意料的,因为建议的作者的意图是涵盖包的所有版本(例如,当为恶意包创建建议时)。
文档还指出,可以通过设置。来抑制这种行为(将所有预发布版本视为正常版本,以便进行范围匹配)includePrereleaseoptions对象上的标志。
因此,使用设置的标志运行相同的检查:
semver.satisfies(“1.2.3-a”、“> 0 ",{“includePrerelease”:1})
返回真正的。
在Bulk Advisory端点中启用这个标志应该可以消除常规npm包版本和预发布版本之间的不一致。
根据JFrog的披露,我们从NPM维护者那里了解到这个功能是预期的行为,因此我们不希望这个行为改变。
概念验证
让我们来cruddl,一个具有严重漏洞的真实软件包(cve - 2022 - 36084)。
在安装cruddl版本2.0.0,输出显示(如预期的那样!)发现了一个严重漏洞:

如果不采用上述方法,我们选择安装同一软件包的预发布版本:

输出(错误!)显示没有发现任何漏洞,即使版本2.0.0-update.2也受到CVE-2022-36084的影响(固定版本是2.7.0)。
攻击者如何滥用这种行为?
威胁行为者可以通过故意将易受攻击或恶意代码植入其看似无辜的包中来利用这种行为,这些包将被其他开发人员包含,因为有价值的功能,或者由于感染技术(如输入错误或依赖混淆)而导致的错误以前的博客文章例如)。
如上所述,如果威胁行为者使用预发布版本格式的包版本,即使这样的代码被报告为恶意/易受攻击,并且创建了一个警告,依赖于恶意/易受攻击包的开发人员将得不到任何通知,因为npm CLI不会报告此类通知的存在。
开发者该如何避免这个问题呢?
我们对开发人员和DevOps工程师的建议是永远不要安装带有预发布版本的NPM包除非他们100%确定该软件包来自一个非常有信誉的来源。即使在这种情况下,我们也建议尽快回到软件包的非预发布版本。
您可以使用以下命令行来确定当前安装的npm包是否带有预发布版本
Linux:
npm list -a | grep - e @[0-9]+\.[0-9]+\.[0-9]+-
Windows:
NPM list -a | findstr -r @[0-9]*\.[0-9]*\.[0-9]*-
与JFrog安全研究保持同步
关注JFrog安全研究团队的最新发现和技术更新安全研究博客文章并在推特上@JFrogSecurity。