本文主要介绍我们团队最近在软件工程顶会ICSE’22中接收的一篇关于NPM生态中软件安全漏洞的传播及其演变分析的工作。
论文地址:https://arxiv.org/abs/2201.03981
论文网站地址:https://sites.google.com/view/npm-vulnerability-study
一、研究背景及动机
随着第三方组件在软件开发过程中的广泛使用,如何妥善管理第三方组件中引入的安全漏洞所带来的安全风险,尤其在21年12月log4j RCE漏洞事件爆发之后,成为DevSecOps过程中广受关注的问题之一。其中,关于安全漏洞的影响传播,尤其是在如NPM这种大规模使用第三方组件的软件生态中,显得尤为重要。
在已有的相关工作中,研究人员主要通过两种方式分析依赖中存在的安全漏洞对用户项目的影响。
通过直接依赖关系反向追踪含有漏洞组件的下游用户
基于依赖关系进行间接依赖的可达性分析
然而,这两种依赖关系均忽视了NPM生态中软件包安装过程中上下文环境对依赖版本选择的影响,从而无法精准地得到安全漏洞在生态中真实的影响范围。
如下图所示,直接以间接依赖的可达性分析,则存在A@1.0.0->C@1.0.0->D@1.2.0->E@1.0.0的间接依赖链 ,如图(b)。
而根据NPM的依赖解析规则,由于A@1.0.0的直接依赖中已经解析并选择了D@1.0.0,而D@1.0.0亦满足C@1.0.0->D的依赖约束范围,此时应直接选择已有的D@1.1.0而不是重新安装D@1.2.0从而引入含有漏洞的E@1.0.0.
二、研究方法
基于此,本文旨在提出一种轻量且能够对安全漏洞影响尽可能准确分析的方法,对NPM生态中存在的安全漏洞进行经验研究。
如上图所示,我们首先设计并实现一整套数据处理平台,对NPM生态中软件包元数据、相关漏洞数据等进行收集、整合,并自动生成并更新一个基于neo4j的图数据库 (DVGraph)。
下图为数据收集处理平台的基本架构:
下图为DVGraph的基本数据模型定义:
(定义细节详见论文或论文网站)
截止到生成经验研究所用的数据库(2019年12月),DVGraph包含114w library,1,094w version,及815个标记好的CVE漏洞,该数据库的存储空间超过15GB。
在此基础上,为了提升依赖解析的速度,我们基于对NPM中软件安装过程的分析,提出一个基于DVGraph模拟安装过程中依赖解析策略的算法(DTResolver),DTResolver能够在对任意数据软件包依赖解析的过程中,识别并找出所有依赖中含有安全漏洞的组件及相应的依赖引入路径。
此外由于NPM中广泛使用依赖约束条件(版本范围)而不是固定版本进行依赖定义,导致依赖安装结果随着时间可能发生变化。如下图中,在B@1.0.1发布后,A@1.0.0的安装过程中,对B的依赖将解析成新发布的版本而不是原有的B@1.0.0, 图中C@1.0.1的发布亦是如此。因此我们在DTResolver的基础上进一步增加了时间约束,使其能够支持在给定项目从其发布前到DVGraph更新时间内任意时刻的依赖树模拟解析。
通过在大规模数据集(10w library version)上与现有依赖解析工具(npm-remote-ls)的对比验证,DTResolver能够完全正确地解析超过90%的依赖树,并正确识别98.1%的依赖树中存在的安全漏洞及92.6%的安全漏洞的引入路径。
三、经验研究
我们从以下两个方面分析NPM中安全漏洞的影响:
安全漏洞是如何通过依赖树影响并传播到整个NPM生态的
安全漏洞在依赖树中的影响是如何随着时间变化的
(一)安全漏洞影响传播分析
我们通过对DVGraph中所有的library及其version (超一千万)的依赖树进行解析并分析,发现:
24.78%的包版本(271w library version)的依赖树中至少存在一个含有安全漏洞的组件,这些包版本来自19.96%的第三方组件包 (22.9w library)。
16.17%的第三方组件包(18.6w library)的最新版本的依赖树中存在至少一个含有安全漏洞的组件。
超过100个含有安全漏洞的组件的最新版本仍然受安全漏洞所影响。
我们发现了25个安全漏洞实际上影响了超过1w个第三方组件包或10w个版本(影响大约1%的生态)。
平均依赖树中每个安全漏洞会通过8条不同的依赖链影响根节点组件。
在受影响的271w个包版本中,超过30%包版本的直接依赖中即存在安全漏洞。
安全漏洞的传播链路大都通过有限的直接依赖影响根节点组件,78.94%的受影响组件(214w)的依赖树中,安全漏洞仅通过不超过3个直接依赖影响根节点项目。
(二)安全漏洞影响传播的演进
由于NPM中组件包更新频繁且依赖复杂,任一组件依赖树随着时间的变化也相当频繁,我们从DTResolver的验证数据集中抽取了5.4w个包版本,进而分析其从发布时间开始到2019年底的依赖树变化,并分析其中安全漏洞影响传播的变化,我们发现:
随着时间的发展,越来越多的包版本的依赖树中逐渐出现安全漏洞,且其中每个包版本依赖树中安全漏洞的数量也在逐渐增加。
其中19.2%的包版本在其发布时依赖树中就已经存在了安全漏洞,而在分析截止时间,该数据变成了33.8%。
在所有依赖树中曾经出现安全漏洞的包版本中,69.8%的包版本在截止时间的依赖树中存在的安全漏洞数量超过了发布时间,而相反情况的只有7.4。
绝大部分依赖树中引入的安全漏洞(93%)是在安全漏洞被发布前就已经存在于依赖树中,而88%的安全漏洞修复版本的发布也是在漏洞发布前。
随着时间的变化,依赖树中60%的安全漏洞在依赖树的动态变化过程中可以逐渐被清除,剩余40%的安全漏洞在实验截止时仍存在依赖树中。此外,这些被清除的安全漏洞在依赖树中仍然存在了371天。
不及时的包版本维护(维护者)及不合理的版本约束范围(使用者)是导致大部分漏洞无法随着依赖树动态变化而自动清除的原因,需要更多的措施和工具来辅助改善生态中不同角色的不合理操作。
(三)应用示例:DTReme
基于我们经验研究的发现,我们结合DTResolver提出一种遍历依赖树解空间从而尽可能修复依赖中存在的安全漏洞的工具DTReme,在DTResolver模拟解析依赖树的过程中:
向前检测:在每次解析依赖关系时,只允许解析到安全版本;
向后回溯:在每次发现无法解析到安全版本时,回退到上一个节点重新选择版本。
基于此,理论上可以遍历整个依赖树的解空间并生成包含安全漏洞最少的依赖树。通过与NPM官方的auditing工具(npm audit fix)对比,我们发现DTReme能够修复更多的安全漏洞。但同时也证明了,依赖树中相当一部分的安全漏洞无法通过版本上的调整清除,这也进一步说明了软件供应链中安全漏洞的修复与治理需要生态中各方的共同努力。
四、一些建议
基于以上发现,我们进一步针对供应链中不同参与者提出了一些可行的建议:
维护者(Provider):
当安全漏洞出现时,尽可能早地发布修复版本;
在NPM registry中尽快deprecate含有已知安全漏洞的版本
针对该组件主流使用的依赖约束,尽可能保证满足这些约束的最高版本是安全的,尤其是在推进到下一个大版本之后;
尽可能频繁地检测自己维护组件的依赖中是否存在安全漏洞,以免下游用户受到影响。
使用者(Consumer):
将依赖锁(dependency lock, i.e., package-lock.json)和周期性依赖更新相结合,以降低完全不更新依赖带来的安全风险和频繁更新带来的兼容性问题;
提高对依赖中存在的安全漏洞的防范意识,尤其是直接依赖中;
采用第三方检测工具,在开发运维的过程中,时刻监控依赖中的安全漏洞。
第三方检测机构(Third-party Auditor):
提供更细粒度的依赖中面向安全漏洞的修复方案:现有的主流厂商仅采用粗粒度的修复方案,如Snyk仅通过调整用户项目的直接依赖来规避依赖树中的安全漏洞,可采用类似DTReme的方案提升修复能力;
提供更准确的安全漏洞可达性/可触发性分析:当前基于组件成分分析的漏洞检测手段中,凡是存在于依赖中的安全漏洞均会被认为存在危害,但这会引入大量的误报,需进一步过滤出真实引入危害的安全漏洞,可进一步引入动态模糊测试、静态函数调用可达性分析等技术,降低依赖树中安全漏洞在真实影响上的误报率。