点击上方蓝字谈思实验室
获取更多汽车网络安全资讯
本文介绍了174个恶意软件包的数据集,这些数据包在开源软件供应链的真实攻击中使用,并通过流行的软件包存储库npm、PyPI和RubyGems分发,对2015年11月至2019年11月的软件包进行了手动收集和分析。
本文还提出了两种通用攻击树,以提供有关将恶意代码注入下游用户的依赖树以及在不同时间和不同条件下执行此类代码的技术的结构化概述。这项工作旨在促进开源和研究社区在未来发展的预防和保障措施。
通常,软件供应链攻击旨在将恶意代码注入到软件产品中。攻击者经常篡改给定供应商的最终产品,以使其带有有效的数字签名,因为该数字签名是由相应的供应商签名的,并且可以由最终用户通过受信任的分发渠道(例如分销商)获得,即网站下载或更新。
此类供应链攻击的一个突出例子是NotPetya,这是一种被隐藏在流行的乌克兰会计软件的恶意更新中的勒索软件。2017年,NotPetya目标是乌克兰公司,但也打击了全球的公司,造成了数十亿美元的损失,据说是当今已知的最具破坏性的网络攻击之一。同年,可从供应商的官方网站下载恶意版本的CCleaner(一种适用于Microsoft Windows系统的流行维护工具),并且超过一个月没有被发现。在此期间,它被下载了约230万次。
供应链攻击的另一种形式是将恶意代码注入到软件供应商产品的依赖项中。这种攻击媒介已经由Elias Levy在2003年进行了预测,并且近年来随着该方案的出现,出现了许多实际攻击。这种攻击成为可能,因为现代软件项目通常依赖于多个开源程序包,这些程序包本身引入了许多可传递的依赖关系。这种攻击滥用了开发人员对托管在常用服务器上的软件包的真实性和完整性的信任,并滥用了鼓励这种做法的自动构建系统。
数千个开源软件项目可能需要单个开源软件包,这使得开源软件包成为软件供应链攻击的非常有吸引力的目标。最近对npm软件包event-stream的攻击表明了此类攻击的潜在范围:只需通过要求原始开发人员接管其维护工作,即可将所谓的攻击者授予显着npm软件包的所有权。当时,event-stream还被另外1600个软件包使用,平均每周被下载150万次。
开源软件供应链攻击可与易受攻击的开源软件包的问题相提并论,后者可能会将其漏洞传递给相关的软件项目,这属于OWASP 前10应用程序安全风险之一。但是,在供应链攻击的情况下,会故意注入恶意代码,攻击者会采用混淆和规避技术来避免被人或程序分析工具检测到。
相关工作主要涉及易受攻击的程序包,这些程序包包含偶然引入的设计缺陷或代码错误,没有恶意。但由于疏忽大意,可能构成潜在的安全风险。与此相反,恶意软件包包含故意的设计缺陷或代码错误,这些缺陷或代码错误在软件生命周期中被谨慎的加以利用或触发。
区分易受攻击的软件包和恶意软件包很重要。如前所述,易受攻击的软件包可能包含设计缺陷或代码错误,这些缺陷或代码错误是无意间但由于疏忽而意外引入的,并且可能构成潜在的安全风险。
从技术上讲,恶意和易受攻击的编码可能相似甚至相同,因此,主要区别在于攻击者的意图。这项工作有两个方面的作用:使用攻击树对可能的攻击进行系统描述,以及在实际攻击中使用的恶意软件包数据集。攻击树能够表示对系统的攻击。攻击的主要目标用作根节点,子节点代表实现该目标的可能方法。
第一个攻击树的目的是将恶意代码注入下游用户的软件供应链,而第二个攻击树的目的是在不同情况下触发其恶意行为。手动分析每个恶意软件包和信息源,并在提供足够信息的情况下将其映射到每个攻击树的节点。如果不存在拟合节点,则添加一个新节点。这种迭代方法确保攻击树的节点代表现实的攻击向量。但是,该方法的缺点是这些树不完整,因为它们不反映尚未观察到或未描述的攻击媒介。
在此期间,对漏洞数据库Snyk、特定于语言的安全建议和研究博客进行了审查,以确定恶意包和可能的攻击向量。注意,这些来源仅提及软件包的名称和受影响的版本,因此,实际的恶意代码必须从其他来源下载。但是,这种恶意程序包通常在相应编程语言的标准程序包存储库中不再可用,例如npm或PyPI。
相反,在可能的情况下,它们是从不推荐使用的镜像,互联网档案和公共研究资料库中检索到的。如果可以检索到恶意软件包的代码,则会对其进行手动分析和分类。这样做是为了确认软件包的恶意软件,将其映射到现有的攻击树或在必要时进行扩展。软件包的恶意版本的发布日期根据Libraries.io(该服务可监视所有主要软件包存储库中软件包的发布)而定。咨询和公共事件报告可用于对恶意程序包进行公开披露。
本节从高层次介绍与开源软件开发项目相关的活动和系统开始,并以两个攻击树为结尾。本届的攻击树是基于在野观察到的实际恶意软件包以及安全研究人员和从业人员描述的潜在攻击和弱点以迭代方式创建的。
通常,攻击树允许对针对任何类型的系统的攻击进行系统的描述。因此,给定树的根节点对应于攻击者的顶级目标,子节点代表实现此目标的替代方法。攻击树的顶级目标是将恶意代码注入软件供应链,从而注入开发项目的依存关系,并在不同情况下触发该恶意代码。
在如上图所示的典型开发环境中,维护者是开发项目的成员,他们管理所描述的系统,提供,审查和批准文稿,定义和触发构建过程。开源项目还从贡献者那里获得代码贡献,维护者可以对其进行审查并合并到项目的代码库中。
构建过程(build process)将摄取项目的源代码和其他资源,并且其目标是产生软件工件。这些工件随后被发布,以使它们可供最终用户和其他开发项目使用。
项目资源位于版本控制系统(VCS)中,例如Git,并将其复制到构建系统的本地文件系统中。在这些资源中有直接依赖项的声明,在构建过程开始时,依赖项管理器会对其进行分析,以建立具有所有直接和传递性依赖项的完整依赖项树。由于在构建期间(例如,在编译时或在测试执行期间)都需要它们,因此它们是从软件包存储库中下载的,这些存储库包括用于Python的PyPI,用于Node.js的npm或用于Java的Maven Central。
在成功构建的最后,程序代码和其他资源被组合到一个或多个构建工件中,这些工件可能会被签名并最终发布。要么是诸如应用商店之类的分发平台,以便最终用户可以使用它们,要么将其打包为其他开发项目的存储库。
这样的项目环境受众多信任边界的约束,并且许多威胁都针对各自的数据流,数据存储和流程。即使仅考虑单个软件项目的环境,管理这些威胁也可能具有挑战性。当考虑具有数十个或数百个依赖项的供应链时,要注意每个单独的依赖项都存在这样的环境。显然,此类项目的组合攻击面比完全由内部开发的软件要大得多。
从攻击者的角度出发,恶意行为者打算通过感染一个或多个上游开源软件包来损害软件项目的构建或运行时环境的安全性,每个上游开源软件包均在与上图相当的环境中开发。在以下各节中,将通过两个攻击树来描述实现此目标的方法,这两个攻击树提供了有关攻击路径的结构化概述,这些攻击路径用于将恶意代码注入下游用户的依赖树并在不同时间或不同条件下触发其执行。
上图所示的攻击树的最高目标是将恶意代码注入到下游软件包的依赖树中。因此,一旦具有恶意代码的软件包在分发平台(例如,分发平台)上可用,就可以实现该目标。软件包存储库成为一个或多个其他软件包的直接或传递依赖项。这样,这种类型的代码注入不同于其他注入攻击,其中许多攻击在应用程序运行时利用安全漏洞,例如,安全漏洞。由于缺乏用户输入清理功能而可能导致缓冲区溢出攻击。要将软件包注入依赖树中,攻击者可能会采用两种可能的策略,即感染现有软件包或提交新软件包。
显然,使用其他人未使用的名称开发和发布新的恶意程序包可以避免干扰其他合法项目维护者。但是,此类程序包必须由下游用户发现并引用,以便最终进入受害者程序包的依赖关系树中。这可以通过使用与现有软件包名称类似的名称(抢注)或通过开发和推广木马病毒来实现。攻击者还可能借此机会重用由其原始的合法维护者撤回的现有项目,程序包或用户帐户的标识符(免费使用)。
第二种策略是感染已经具有用户、贡献者和维护者的现有软件包。攻击者可能出于各种原因选择软件包,例如大量或特定的下游用户组。但是,到目前为止收集的数据尚无法验证相应的假设。一旦攻击者选择了要感染的软件包,就可以将恶意代码注入到源中,在构建过程中或注入到软件包存储库中。
开源项目通过社区的贡献来生存和奋斗,因此,攻击者可以模仿良性的项目贡献者。例如,攻击者可能通过创建带有错误修复或看似有用的功能或依赖项的拉取请求(PR)来假装解决现有问题。后者可以用来创建对使用先前描述的技术从头创建的攻击者-控制器程序包的依赖关系。无论如何,此PR必须由合法的项目维护者批准并合并到主代码分支中。或者攻击者可能会通过使用脆弱或受到破坏的凭据或对安全敏感的API令牌将攻击者的全部恶意代码提交给项目的代码库。
此外,攻击者可以通过社会工程成为维护者。在任何情况下,无论恶意代码如何添加到源中,无论在哪里进行构建,它都将在下一个发行版本中成为正式软件包的一部分。与对构建系统和软件包存储库的攻击相比,VCS中的恶意代码更易于手动或自动查看提交或整个存储库。
构建系统的折衷通常需要篡改在整个构建过程中使用的资源,例如编译器,构建插件或网络服务(例如代理或DNS服务器)。如果构建系统(无论是开发人员的工作站还是Jenkins之类的构建服务器)容易受到漏洞攻击,或者如果通信渠道不安全以致攻击者可以操纵从存储库中下载软件包,这些资源可能会受到损害。目标软件包的发布版本也可以在共享的构建系统上运行,因此可以被多个项目使用。
根据设置的不同,此类构建过程可能不是孤立运行的,因此,包缓存或构建插件之类的资源会在不同项目的构建之间共享。在这种情况下,攻击者可能会在其控制下的恶意构建项目期间破坏共享资源,从而使目标项目在以后的时间点受到破坏。
即使是流行的软件包系统信息库,也仍然会遭受简单但严重的安全漏洞。尽管所有其他攻击媒介都试图将恶意代码注入到单个程序包中,但利用程序包存储库中的漏洞本身会使带有其所有程序包的整个存储库处于危险之中。
类似于在源代码中注入代码,攻击者可能会使用脆弱或受到破坏的凭据或通过社会工程获得维护者授权,以发布合法版本的恶意版本。由于前者已被用于多种攻击中,因此诸如核心基础设施(Core Infrastructure Initiative)的徽章计划之类的计划向项目维护者提供了正式建议,以启用双因素身份验证。
此外,攻击者可能会将恶意程序包版本上载到原始维护者未提供的备用存储库或存储库镜像,并等待受害者从那里获取依赖关系。据说此类存储库和镜像不那么受欢迎,攻击取决于受害者的配置,例如查询依赖关系或使用镜像的存储库顺序。
一旦某个项目的依赖关系树中存在恶意代码,上图所示的攻击树就具有在不同条件下触发恶意代码的顶级目标。这样的条件可以用来逃避对特定用户和系统的检测和/或目标攻击。
恶意代码可能在受感染的软件包及其下游用户的不同生命周期阶段触发。如果测试用例中包含恶意代码,则攻击主要针对被感染程序包的贡献者和维护者,他们在其开发人员工作站和构建系统上运行此类测试。
在许多记录的攻击中,恶意代码被包含在安装脚本里,安装脚本在软件包安装期间由下游用户或其依赖关系管理器自动执行。此类安装脚本适用于Python和Node.js,可用于执行安装前或安装后活动。安装脚本中的恶意代码使下游程序包的提供者和维护者及其最终用户受到威胁。恶意代码也可能在下游程序包的运行时触发,这要求将其作为受害者程序包的常规控制流的一部分进行调用。
在Python中,这可以通过在init .py中包含恶意代码来实现,该恶意代码通过import语句调用。在JavaScript中,这可以通过猴子补丁(monkey-patching)等现有方法来实现。细化此目标可以轻松涵盖各个编程语言,程序包管理器等的细节。
与生命周期阶段无关,恶意行为的执行可能总是触发(无条件的)或仅在满足某些条件时(有条件的执行)触发。对于任何其他恶意软件,条件执行会使恶意开源软件包的动态检测复杂化,因为在沙箱环境中可能无法理解或满足相应条件。以应用程序状态为条件的执行是逃避检测的常见手段,例如在测试环境或专用的恶意软件分析沙箱中。同样,各个构建系统的细节可能会包含在各自的子目标中,例如Jenkins环境变量的存在表明恶意代码是在构建期间而不是在生产环境中触发的。
此外,条件可能与特定的受害者包有关,例如检查特定的应用程序状态,例如加密钱包的余额。大量重复使用开放源代码软件包可能导致以下事实:恶意软件包最终出现在许多下游软件包的依赖关系树中。如果攻击者只对某些软件包感兴趣,则它们可能会在手边给定依赖关系树的节点上限制代码执行。此外,所使用的操作系统可以作为条件。
总共可以识别469个恶意软件包。此外,还发现了59个可被确认为POC的软件包(由研究人员发布),因此不再进行进一步检查。最终,能够为174个软件包获得至少一个受影响的版本。Npm的恶意软件包成功下载率为109/374(29.14%),PyPI为28/44(63.64%),RubyGems为37/41(90.24%)和Maven Central为0/10(0.00%)。
该数据集包含在npm上发布的62.6%的程序包,因此是用JavaScript为Node.js编写的。其余的软件包通过PyPI(16.1%,Python)和RubyGems(21.3%,Ruby)发布。不幸的是,无法下载针对Android开发人员的恶意Java软件包。对于PHP,根本无法识别任何恶意软件包。
完整的数据集可在GitHub上免费获得:https://dasfreak.github.io/Backstabbers-Knife-Collection 。出于道德原因,仅在有正当理由的情况下才允许访问。数据集的结构如下:package-manager/package-name/version/package.file。恶意软件包在第一级由其原始软件包管理器分组。此外,一个软件包的多个受影响版本会在相应软件包名称下。事件流受影响版本示例:npm/event-stream/3.3.6/event-stream-3.3.6.tgz。
上图可视化了收集到的软件包的发布日期,范围从2015年11月到2019年11月。发布和披露日期是根据软件包的上载时间和相应通报的发布日期来标识的,这些通报将相应版本标识为恶意。显然,已发布的恶意软件包数量呈增长趋势。虽然已知用于PyPI的恶意程序包可以追溯到2015年,并且此后一直在增加,但npm在2017年获得了大量恶意程序包,而RubyGems上的恶意程序包在2019年经历了最高点。
上图显示在被公开报告之前,平均有209天的恶意软件包可用(min=-1,max= 1216,ρ= 258,x= 67)。尽管知道npm/eslint-scope/3.7.2的感染,但由于开发人员的重新打包策略,该软件包仍在使用中。npm/rpc-websocket/0.7.7最大值达到了1,216天,它接管了一个废弃的软件包,很长时间没有被发现。
总的来说,这表明软件包倾向于长期可用。虽然PyPI的平均在线时间最高,但该时间段的npm变化最大,而RubyGems倾向于更及时地检测到恶意软件包。
程序包的恶意行为可能在与程序包交互的不同点触发。最典型地,可以安装、测试或执行软件包。每个软件包存储库的分离如下图所示。它说明了在安装过程中对任意代码的错误处理会产生使用最多的感染媒介。
显然,大多数恶意软件包(56%)会在安装时启动其例程。这可以由软件包存储库的安装命令触发,例如npm install
与此相反,Ruby没有实现这种安装逻辑,Ruby中不存在该情况的软件包。因此,在RubyGems上找到的所有包都将运行时用作触发器。总体而言,有43%的程序包在程序运行时(即从其他函数调用时)暴露了其恶意行为。
对于1%的软件包,测试程序被用作触发器。调用npm/ladder-text-js/1.0.0的测试例程将执行sudo rm-rf / *,这可能会删除所有文件。
如上图所示,有41%的软件包在检查条件之前会触发进一步的执行。这可能取决于应用程序的状态,例如检查主应用程序是否处于生产模式(例如RubyGems/paranoid2/1.1.6),域名的可解析性(例如npm/logsymbles/2.2.0)或加密钱包中包含的金额(例如npm/flatmap- stream/0.1.1)。
其他技术是检查依赖关系树中是否存在另一个软件包(例如npm/load-from-cwd-or-npm/3.0.2)或该软件包是否在某个操作系统上执行(例如PyPI / libpeshka / 0.6)。
在PyPI和RubyGems上发布的大多数软件包都是无条件执行的。对于npm,条件执行和无条件执行的比率几乎相等。
在上图中,很明显,大多数(61%)恶意软件包都是通过域名抢注来模仿现有软件包的名称的。对该现象的更深入分析显示,平均抢注程序包到目标的Levenshtein距离为2.3(,,min= 0,max= 11,ρ= 2.05,x= 1.0)。在某些情况下,可以从其他软件包存储库中获得抢注目标。完全相同名称的Linux软件包系统信息库apt。例如python-sqlite就是这种情况。在以kafka-python为目标的pythonkafka的情况下,最大距离为11。常用的技术是添加或删除连字符,省略单个字母或交换经常被错误键入的字母。经常被针对的单词是,,color”或与之对应的英式英语单词,,color”。
第二种最常见的注入方法是感染现有包装。这通常可以通过存储库系统的凭据受损(例如npm/eslint-scope/3.7.2)来实现。在大多数情况下,无法回顾确切的感染技术。这是因为相关的来源通常会从版本控制系统中删除,或者没有进一步公开有关注入的详细信息。因此,这些软件包被列为感染现有软件包。
另一种注入技术是创建一个新软件包,其中仅包含木马恶意软件包。在这些软件包中找不到有意义的拼写抢注目标。这些软件包可以与受感染的现有软件包结合使用,也可以独立使用。
如下图所示,大多数软件包都针对数据渗透。通常,感兴趣的数据是/etc/passwd,~/.ssh/*,~/.npmrc或~/.bash历史记录的内容。此外,恶意程序包试图泄漏环境变量(其中可能包含访问令牌)和常规系统信息。另一个流行的目标是语音和文本聊天应用程序Discord的令牌。Discord用户的帐户可能会链接到信用卡信息,因此可用于财务欺诈。
此外,有34%的软件包充当Dropper来下载第二阶段的有效负载。另有5%的用户利用后门(即反弹shell)到远程服务器,并等待进一步的说明。3%的目的是通过用fork炸弹和文件删除(例如npm/destroyer-of-worlds/1.0.0)耗尽资源或破坏其他软件包的功能(npm/load-from-cwd-or-npm/3.0.2)。3%将财务收益作为主要目标,如在后台运行加密矿工(npm/hooka-tools / 1.0.0)或直接窃取加密货币(例如pip/colourama/0.1.6)。另外,可能会发生上述目标的组合。
为了识别目标操作系统,对源代码进行了手动分析,以获得可能像if platform.system() is “Windows” 一样构造的提示,例如使用PyPI/openvc/1.0.0 或者通过依赖仅在某些OS上可用的资源而隐含的。这些资源可能是包含敏感信息的文件,如.bashrc等(npm/font-scrubber/1.2.2)或可执行文件,如/bin/sh(npm/rpc-websocket/0.7.11)。
对针对其目标操作系统(OS)的软件包的分析表明,大多数软件包(53%)是不可知的,即不依赖于操作系统特定的功能。该分析是对程序包的初始可见代码进行的,因此第二阶段有效负载的目标OS仍然未知。但是,由于构建环境通常是在此类OS上运行的,因此与Unix类似的系统似乎比Windows和macOS更具针对性。
只有一种已知的macOS案例是目标,其中软件包npm/angluar-cli/0.0.1通过删除和修改macOS的McAfee病毒扫描程序对macOS进行拒绝服务攻击。
恶意行为者经常试图掩盖其代码的存在,即阻碍其被肉眼看到。在数据集中将近一半的软件包(49%)采用了某种混淆处理。大多数情况下,使用不同的编码(Base64或Hex)来掩盖恶意功能或可疑变量(例如域名)的存在。
良性软件包经常使用的一种压缩源代码并节省带宽的技术是最小化的。但是,这对于恶意行为者来说是一个机会,可以潜入人类无法读取的额外代码(例如npm/tensorplow/1.0.0)。隐藏变量的另一种方法是使用字符串采样。这需要一个看似随机的字符串,该字符串用于通过逐个字母的选择来重建有意义的字符串(例如npm/ember-power-timepicker/1.0.8)。
在一种情况下,恶意功能被加密隐藏。软件包npm/flatmap-stream/0.1.1利用AES256和目标软件包的简短描述作为解密密钥。这样,恶意行为仅在目标软件包使用时才暴露出来。此外,存在上述技术的组合。
为了推断攻击活动的存在,对所有软件包进行了分析,以重新使用恶意代码或依赖关系。这样,有可能识别出21个群集,至少有两个程序包具有相同的恶意代码,或者由攻击者控制的程序包依赖于另一个具有实际恶意代码的程序包,这些群集至少有两个。总共174个软件包中的157个(占90%)属于一个集群。平均而言,群集包括7.28个程序包(min= 2,max= 36,ρ= 8.96,x = 3)。
对一个群集中的包装的出版物发布日期进行交叉比较发现,发布之间的平均时间间隔为42天,6:50:18(min=1:29:40,max= 353天,11:17:02,ρ= 78天,0:43:10,x = 7天,15:24:51)。最大的集群形成在crossenv的36个软件包,平均时间间隔为5.98天。它分两期发布,2017年7月19日在15分钟内发布了11个软件包,2017年8月1日在30分钟内发布了另外25个软件包。
发布日期间隔为353天的集群由两个软件包PyPI/jeilyfish /0.7.0和PyPI/python3-dateutil /2.9.1组成。第一次发布于18/12/11 12:26 AM,其中包含的代码可以下载脚本以从Windows计算机中窃取SSH和GPG密钥。直到第二个软件包在19/29/19 11:43 AM发布之后很长时间都未被检测到,该软件包本身并不包含恶意代码,但引用了第一个软件包。在19/12/19 05:53 PM被报告并删除了该群集。
尽管大多数群集只包含一个软件包存储库中的软件包,但是可以找到一个群集,其中主要包含npm中的软件包,以及RubyGems中的RubyGems/active-support/5.2.0。这意味着存在攻击活动,或者至少技术跨多个软件包存储库流动。
根据对代码相似性的手动评估,上图的npm/jqeury/3.3.1(左)和RubyGems/active-support/5.2.0(右)都属于同一集群,即使它们发布在不同的存储库中。
从攻击者的角度来看,程序包存储库代表了可靠且可扩展的恶意软件分发渠道。到目前为止,Node.js(npm)和Python(PyPI)的存储库是恶意软件包的主要目标,这可能是由于在软件包安装过程中可以轻松触发恶意代码这一事实。已经存在许多可以由不同利益相关者实施的对策,例如面向开放源代码维护者的多因素身份验证,面向开放源代码用户的版本固定和禁用安装脚本,或者隔离构建过程和增强构建服务器。
但是,尽管提高了利益相关者的普遍意识,这种对策必须更易于访问,并且在可能的情况下默认实施,以防止开源软件供应链攻击。
从观察到的案例和相关工作中得出了两个攻击树。一种用于将恶意程序包注入开源生态系统,另一种用于执行恶意代码。这些攻击树可对过去和将来的攻击进行系统描述。能够创建第一个手动管理的恶意开源软件包的数据集,该数据包已在现实世界的攻击中使用。从2015年11月到2019年11月,它包含174个恶意软件包(npm 62.6%,PyPI 16.1%,RubyGems 21.3%)。
手动分析显示,大多数软件包(56%)会在安装时触发其恶意行为,另有41%使用进一步的条件确定是否运行。超过一半的软件包(61%)利用域名抢注将自身注入到生态系统中,而数据泄露是最常见的目标(55%)。这些软件包通常与操作系统无关(53%),并且经常采用混淆处理(49%)来隐藏自身。最终可以通过不同的编程语言,通过重用的代码来检测恶意软件包的多个群集。
希望有新的技术和工具来扫描整个程序包存储库中的可疑程序包,例如根据观察发现恶意代码可在同一广告系列的程序包甚至语言之间重复使用。在这种情况下,手动管理和标记的数据集允许有监督的学习方法,这些方法支持对恶意软件包的自动化和整个存储库范围内的搜索。此外,关于现有和新的缓解策略,本文提出的数据集可作为基准。