供应链污染:为CTF挑战追踪每周1600万次下载的npm包漏洞
背景
GovTech网络安全小组最近在2020年12月4日至6日举办了STACK the Flags网络安全夺旗赛(CTF)。对于Web领域,我的团队希望构建能解决我们在政府Web应用和商业现成产品渗透测试中遇到的实际问题的挑战。
根据我的经验,大量漏洞源于开发人员对代码中使用的第三方库缺乏了解。如果这些库被恶意攻击者破坏或以不安全的方式应用,开发人员可能会在不知情的情况下给应用带来毁灭性弱点。SolarWinds供应链攻击就是典型例子。
作为Web开发人员最流行的编程语言之一,Node.js生态系统在第三方库方面也存在不少问题。Node包管理器(npm)每月提供超过1000亿个包,托管近150万个包。包管理器如此庞大的部分原因在于树状依赖结构。每次在项目中安装一个包时,也会安装该包的依赖项,以及它们的依赖项,依此类推——有时最终会安装数十个包!
如果此链中的单个依赖项被破坏或存在漏洞,可能会对整个生态系统产生连锁影响。2018年,一个广泛使用的npm包event-stream被恶意作者接管,添加了针对Copay比特币钱包的比特币窃取代码。尽管攻击者只有一个目标,但在恶意代码被发现前的2.5个月内,流行的event-stream包被下载了近800万次。2019年,我在Black Hat Asia上展示了一个名为npm-scan的工具,旨在识别恶意包,但很明显npm需要系统性地解决这个问题。幸运的是,自那以后npm生态系统有了显著改进,包括发布了npm audit功能和更积极的监控。
寻找NPM包漏洞
考虑到这一背景,我开始设计一个使用易受攻击的npm包的挑战。此外,我想利用原型污染漏洞。简而言之,原型污染涉及通过污染对象的原型来覆盖应用程序中Javascript对象的属性。例如,如果我覆盖了对象的toString属性并用console.log打印该对象,它将输出我覆盖的值而不是该对象的实际字符串表示。这可能导致关键问题——想象一下,如果我将用户对象的isAdmin属性覆盖为始终为true会发生什么!然而,由于原型污染的影响仍取决于应用程序上下文,很少有人知道如何正确利用它。
接下来,我应用两种策略来查找易受原型污染攻击的npm包:模式匹配和功能分组。
模式匹配
当编写易受攻击的代码时,它通常落入可识别的模式,这些模式可以被静态扫描器捕获。这构成了许多工具的基础,例如GitHub的CodeQL,它扫描开源代码库以查找不安全的代码模式。虽然扫描器用于防御性地提前发现漏洞,但攻击者也可以执行自己的模式匹配以在开源代码中发现未报告的漏洞。
我选择的工具是grep.app,这是一个快速的正则表达式搜索引擎,可搜索GitHub上超过50万个公共仓库。由于大多数npm包在GitHub上托管其代码,我相信它会发现至少几个易受攻击的包。下一步是识别有用的正则表达式模式。我查找了先前披露的npm包中的原型污染漏洞,并找到了2020年1月Snyk对dot-prop包的咨询。接下来,我检查了修补该漏洞的GitHub提交。
dot-prop通过黑名单以下键来修补原型污染漏洞:
|
|
这里没有明显易受攻击的代码模式;正是缺乏黑名单使其易受攻击。我决定稍微放大范围,专注于dot-prop最初需要黑名单的功能。根据包描述,dot-prop是一个使用点路径从嵌套对象获取、设置或删除属性的包。
例如,我可以这样设置属性:
|
|
然而,以下概念验证将使用dot-prop的set函数触发原型污染:
|
|
这是因为dot-prop的功能是将点路径字符串解析为对象中的键并设置这些键的值。根据我们对原型污染的了解,这本质上是危险的,除非某些键被列入黑名单。
考虑这一点后,我决定搜索匹配其他点路径解析器的模式。dot-prop使用path.split('.')
来分割点路径,尽管我后来发现其他包也常用key.split('.')
。通过这种方法,我发现了几个易受攻击的包,但这需要我手动检查每个包的代码以验证是否使用了黑名单。此外,并非所有点路径解析器都使用key或path来表示点路径字符串,因此我可能错过了更多。
功能分组
我意识到更好的方法是根据功能对npm包进行分组——在前一种情况下是点路径解析器。这是因为除非放置适当的黑名单或保障措施,否则此类功能默认是不安全的。在查看了点路径解析器之后,我偶然发现了一组更丰富的包——配置文件解析器。
配置文件有各种格式,如YAML、JSON等。其中,TOML和INI非常相似并匹配此格式:
|
|
典型的INI解析器会将此文件解析为以下对象:
|
|
然而,除非解析器设置黑名单,否则以下配置文件将导致原型污染:
|
|
然而,除非解析器使用黑名单,否则以下配置文件将导致原型污染:
|
|
确实,先前在此类解析器中已报告过原型污染漏洞,但仅是临时性的。我构建了概念验证代码以快速大规模测试包,然后使用npm的搜索功能发现其他解析器。搜索功能支持按标签搜索,如keywords:toml或keywords:toml-parser,使我能够快速发现多个易受攻击的包。
其中之一是ini,一个简单的INI解析器,每周下载量惊人地达到1600万次:
这是因为近2000个依赖包使用ini,包括npm CLI本身!由于npm随每个默认Node.js安装包一起提供,这意味着每个Node.js用户也在下载易受攻击的ini包。其他值得注意的依赖项包括Angular CLI和sodium-native(libsodium加密库的包装器)。虽然这些包将ini作为依赖项包含,但它们的风险取决于ini的使用方式;如果它们不调用易受攻击的函数,则不会触发漏洞。
尽管我没有在挑战中使用ini,但我确保负责任地向npm披露了易受攻击的包列表。
负责任披露
npm支持健全的负责任披露流程,包括当前暂停的漏洞披露计划。开源安全公司Snyk也提供了一个简单的漏洞披露表单,我用来协调披露。幸运的是,ini的披露过程顺利进行,开发人员在两天内修补了漏洞。
- 2020年12月6日:向Snyk初步披露
- 2020年12月7日:Snyk首次回应
- 2020年12月8日:向开发者披露
- 2020年12月10日:发布补丁
- 2020年12月10日:披露发布
- 2020年12月11日:分配CVE-2020–7788
其他包正在 undergoing负责任披露或已被披露,例如multi-ini。
漏洞狩猎过程突出了开源包的优势和弱点。尽管可以分析第三方编写的开源包是否存在漏洞或被恶意攻击者破坏,但开发人员也可以快速发现、报告和修补漏洞。在使用包之前审查包仍然是组织和开发人员的责任。虽然并非每个人都能负担直接检查代码所需的资源,但有免费工具如Snyk Advisor,使用更新频率和贡献历史等指标来估计包的健康状况。开发人员还应审查包的新版本,特别是如果它们由不同作者编写或在非正常时间发布。
从长远来看,开源包安全没有简单的答案。然而,组织可以应用合理的措施来有效保护其项目。
附言:我们的参与者之一Yeo Quan Yang发布了一篇优秀的挑战文章,说明了将包中的原型污染与模板引擎中的远程代码执行小工具链接的预期解决方案。在此查看!