A Needle is an Outlier in a Haystack: Hunting Malicious PyPI Packages with Code Clustering
Institute of Software, Chinese Academy of Sciences, Beijing, China;University of Chinese Academy of Sciences, Beijing, China
Abstract
作为最流行的Python软件存储库,PyPI已经成为Python生态系统中不可或缺的一部分。遗憾的是,PyPI 的开放性使最终用户面临来自恶意软件包的巨大安全风险。因此,及时有效地识别大量新上传的 PyPI 包中的恶意软件已成为紧迫问题。现有的检测方法依赖于难以获得的显性知识,例如污点源、接收器和恶意代码模式,这使得它们很容易忽视新出现的恶意包。
在本文中,我们提出了一种轻量级且有效的方法,即 MPHunter,无需任何明确的先验知识即可检测恶意包。 MPHunter 建立在两个基本且富有洞察力的观察之上。首先,恶意软件包比良性软件包少得多;其次,恶意软件包的安装脚本的功能与良性软件包的功能有很大不同,后者经常形成集群。因此,MPHunter 利用集群技术对 PyPI 包的安装脚本进行分组并识别异常值。随后,MPHunter 根据离群值以及离群值与已知恶意实例的距离对离群值进行排序,从而有效突出潜在的恶意包。
借助 MPHunter,我们在两个月内从 31,329 个新上传的软件包池中成功识别出 60 个以前未知的恶意软件包。所有这些都已得到 PyPI 官方的确认。此外,手动分析表明 MPHunter 可以识别所有潜在的恶意安装脚本,并且对所有分析的软件包的召回率达到 100%。我们断言 MPHunter 为现有检测技术提供了有价值且有利的补充,扩大了软件供应链安全分析的库。
Introduction
作为最流行的开发语言,Python 由于其简单性、多功能性和易用性,近年来得到了广泛的采用。拥有庞大而活跃的开发人员社区,众多的软件包有助于参与广泛的项目。软件存储库在当代软件开发过程中发挥着至关重要的作用,使开发人员能够轻松下载和重用第三方代码,从而大大提高开发效率。 Python 软件包索引 (PyPI) 是 Python 的官方第三方软件存储库,正在成为开发人员日益重要的资源。当时截至撰写本文时,PyPI 上有 452,296 个项目,涉及 4,436,865 个版本和 8,151,115 个文件。
PyPI 为开发人员提供了发布软件包的便捷平台,使用户可以轻松下载和重用它们。然而,PyPI 的开放性使其面临严重的恶意软件威胁。攻击者可以上传恶意软件包,并通过软件供应链迅速扩散到下游项目,给无数用户造成重大损失。根据 Sonatype 报告,过去三年软件供应链攻击数量激增 742%。最近,PyPI 面临着越来越多的攻击。为了在恶意软件包造成重大危害之前防止其传播,准确、快速地检测 PyPI 上新上传的软件包至关重要。
值得注意的是,攻击者通常希望尽快执行他们的攻击向量。正如中所报告的,96% 的 PyPI 恶意包在安装时启动其有效负载。 PyPI包的安装脚本setup.py在攻击中起着至关重要的作用。攻击者可以将其有效负载注入 setup.py,从而在用户安装恶意包时触发它。事实上,一些粗制滥造的恶意包只包含 setup.py 脚本。
人们提出了许多方法[8]-[21],通过对代码、元数据和动态行为应用各种分析技术来检测软件存储库中的恶意包。现有方法依赖于显式检测知识,包括安全敏感功能(例如污点源、接收器、危险方法)、恶意代码模式和行为模式。检测器已识别出许多与已知检测知识相匹配的恶意软件包。
遗憾的是,获得明确的检测知识是一项昂贵的任务。研究人员主要依靠对已知恶意软件的手动分析来收集所需的知识,这既乏味又耗时。更重要的是,知识获取方式从根本上来说是被动的。为了绕过检测,攻击者可以采用超出已知检测规则的技术来实现其有效负载。提前准备一套全面的检测规则是不可行的。
因此,现有的检测器可能会忽略新出现的恶意软件包。根据我们的实验,由于缺乏相关的检测规则,即使是最先进的检测器 MalOSS 也无法检测到一些新兴的恶意包。此外,一些攻击还采用逃避检测的复杂技术。例如,Check Point Research 最近在 PyPI 上发现了一个新的、独特的恶意包 apicolor-1.2.4,它导入了一个未公开的图像隐写模块 Judyb,以将其攻击向量隐藏在 setup.py 中。对于分析师来说,在遇到恶意软件之前获取有关 Judyb 的知识几乎是不可能的。
一个重要的问题是如何在不依赖显性知识的情况下检测恶意包。在分析了一些恶意样本后,我们发现有两个可利用的事实。首先,恶意软件包比正常软件包要少得多,就像大海捞针一样。尽管 PyPI 上出现了大量恶意软件包,但绝大多数 PyPI 软件包仍然是良性的。其次,恶意安装脚本的功能与良性安装脚本的功能有很大不同,后者经常形成集群。 setup.py 脚本专用于配置和管理包的构建、发布和安装。因此,良性 setup.py 脚本的活动往往是相似的。然而,恶意安装脚本会执行正常 setup.py 中不希望出现且很少出现的危险操作,例如访问敏感信息和安装后门。基于这些观察,我们引入了一种名为 MPHunter(恶意包猎人)的新方法来检测恶意 PyPI 包。与现有研究相比,MPHunter 直接利用现成的良性软件包和已知的恶意样本,避免了提取显式检测知识所需的繁琐且容易出错的手动分析。
如图 1 所示,MPHunter 最初采用聚类技术对 PyPI 包的安装脚本进行分组并识别异常值,即与大多数(良性)脚本不同的脚本。随后,根据异常值以及它们与已知恶意安装脚本之间的距离对异常值进行排名。突出显示可疑对象以进行审核以识别恶意软件包。
MPHunter 的核心组件是一个称为规范代码嵌入模型(CCEM)的代码嵌入模型。 CCEM 使用从现实世界的 Python 项目中提取的规范函数调用序列进行训练。通过结合 NLP 的文档嵌入技术,它可以将输入脚本编码为低维稠密向量。这可以实现高效、准确的聚类和相似性对向量空间中的目标安装脚本进行测量,识别异常值并对其进行排名。因此,我们可以从大量新上传的 PyPI 包中有效地检测出恶意包。
我们使用 MPHunter 检测了 30 批 PyPI 软件包,总计 31,329 个软件包。从其中 15 个批次中,我们检测到 60 个以前未知的恶意软件包。所有检测到的 60 个软件包均已被确认为真正的恶意软件并被 PyPI 删除(第 IV-B 节)。实验还表明,MPHunter 的召回率对于检测目标批次中的恶意 setup.py 来说是完美的(100%)。对于不需要显性知识的检测方法来说,这个结果是非常令人鼓舞的。此外,MPHunter 已被证明具有可扩展性,只需几分钟即可完成大多数批次的分析。
本文的贡献如下:
- 一种用于检测 PyPI 上的恶意包的无显式知识且可扩展的方法。
- 用于准确、高效的代码聚类和排序的嵌入模型。
- 从PyPI 存储库中检测到60 个以前未知的恶意软件包。我们的数据集和源代码可在 https://github.com/rwnbiad105/MPHunter 上公开获取。
Background
- Package distribution 包分发
开发人员可以利用自动化工具来创建准备发布的工件。 PyPI 通过对包上传施加最小限制来鼓励更多开发者参与 Python 生态系统。要上传包,开发人员只需在 PyPI 上创建一个帐户即可。上传的工件要经过存储库的安全审查。对上传的工件执行多项安全检查,以识别危险行为。如果上传的工件通过安全审查,它将保留在 PyPI 上。不到 10 名 PyPI 管理员负责监督超过 400,000 个包所有者。 PyPI 的安全审查仅检查 setup.py 文件,但许多恶意 setup.py 脚本仍然设法绕过 PyPI 的审查管道。
- Installation script 安装脚本
当用户安装Python包时,会自动调用setup.py脚本。 setup() 函数是从 setuptools 模块导入的,是脚本的核心函数。在安装过程中,该函数会执行一系列操作,例如设置包的元数据、安装依赖项以及生成安装脚本。开发者可以通过指定setup()函数的cmdclass参数来自定义操作。例如,cmdclass 参数可用于指定软件包安装后要执行的命令类。此外,setup.py 中包含的其他单独函数和命令在软件包安装过程中按顺序执行。
在普通的setup.py脚本中,会执行类似的操作,比如调用find packages()函数来搜索环境中可用的包。但是,在恶意 setup.py 中,会插入攻击向量来执行有害操作。常见的方法包括在cmdclass参数中指定安装后执行的恶意命令,以及直接将恶意函数或指令导入到安装过程中触发的脚本中。恶意操作涉及访问敏感信息、执行解码字符串、分叉新进程等,这些在良性安装脚本中很少发现。
Methodology
A. 概述
图 2 说明了 MPHunter 的流程。 MPHunter 由以下四个步骤组成。首先,通过使用词嵌入提前构建 API 编码模型(称为 APIEM)。该模型用于协助从目标脚本中提取规范的 API 调用序列作为 CCEM 的训练样本。其次,在APIEM的指导下,使用深度优先搜索(DFS)和广度优先搜索(BFS)两种遍历策略来探索PyPI包脚本的CFG和CG。提取规范的 API 序列来构建 CCEM。随后,我们使用CCEM嵌入新上传的PyPI包和预先收集的良性包的setup.py。获得的嵌入向量使用 HDBSCAN 进行聚类以识别异常值,即恶意安装脚本候选者。在最后一步中,通过测量候选者与良性样本以及已知恶意样本之间的距离来对候选者进行排名。这使我们能够选择最有可能的嫌疑人进行审核,以检测新上传的恶意软件包。远离良性样本但接近已知恶意样本的候选者将被考虑进行手动分析。
B. 构建 APIEM
C. 构建 CCEM
D. 聚类
E. 排名
Evaluation
为了评估 MPHunter,我们调查了以下三个研究问题:
RQ1:MPHunter 能否有效检测未知的恶意 PyPI 包?
RQ2:有多少潜在的恶意安装脚本被遗漏,即MPHunter 的召回率是多少?
RQ3:MPHunter 的可扩展性如何?
A. 数据集
我们收集了以下三个数据集用于评估 MPHunter。
训练集。我们从 PyPI 存储库中随机下载了 10,000 个包,并提取了 197,667 个 Python 脚本作为训练集来训练 APIEM 和 CCEM。
良性样本集。我们随机下载了 10,000 个存活时间在 PyPI 存储库中超过了 90 天,并从中提取了 9,437 个安装脚本作为良性示例集(某些包没有 setup.py)。
恶意样本集。我们分别从中获得了462个已知恶意包,从[7]中获得了433个包版本。从其中提取 874 个 setup.py 脚本来对候选者进行排名。
B. 有效性
C. 召回率
MPHunter 能够检测到的潜在恶意包的比例,即召回率,也是一个值得关注的问题。为了获得召回率,需要精确地知道数据集中恶意包的数量。在一些实验数据集中,恶意包已被明确标记。然而,当面对大量未标记的目标包时,这是一项非常耗时且具有挑战性的任务。
尽管一些极其复杂的恶意包仍可能逃脱人工分析,但上述艰苦的实验表明MPHunter在现实场景中具有可靠的召回率。我们还使用已知的恶意样本评估了 MPHunter 的召回率。我们从恶意包数据集中随机选择100个样本,将其分为10组,并将剩余的333个样本作为已知恶意集M。我们随机下载1000个良性包作为背景集,并将每组测试样本混合与他们形成10个测试集。如表二所示,100 个恶意样本中有 92 个被 MPHunter 评为前十名(召回率为 92%)。这表明 MPHunter 在更具代表性的数据集上也有不错的表现。
D. 可扩展性
MPHunter的流程可以分为两个阶段:离线和在线,分别对应模型训练和检测。我们分别跟踪每个阶段所需的运行时间......
E. Case Studies 案例研究
情况#1:未知的源和汇。一些攻击者已经开始使用非标准库来实现恶意负载以逃避检测。以我们检测到的恶意包syntaxcode-0.0.0为例,其部分payload如图7(a)所示。攻击者使用第三方库方法 ImageGrab.grab() 截取屏幕截图,然后使用 Discord.File() 将其写入文件,并使用自定义方法 ctx.send() 传输到远程控制端点。检测器可能不知道这些非标准方法。例如,在MalOSS[20]中,其检测知识库[41]没有配置相关的源和汇。这可能会导致丢失恶意软件包。事实上,MalOSS 未能将该软件包识别为恶意软件包。甚至一些广泛使用的、可利用的非标准方法也被省略。如图7(b)所示,检测到的恶意包virtuallenv-20.16.4利用流行的第三方方法requests.get()下载恶意可执行文件并执行它。不幸的是,该方法也没有在知识库中配置,导致报告丢失。因此,可以看出基于显式检测知识的检测方法面临重大挑战。
案例#2:欺骗人类审计员。攻击者还可以采用一些方法来逃避手动分析。我们发现了一些微妙且有趣的恶意软件包。在#17批次中,我们捕获了一个恶意软件包henter1.0.0,它与七个类似的软件包混合在一起。这八个包在很短的时间内就上传到了PyPI。我们认为,攻击者通过这种方式上传这些包,是为了利用多个类似但无害的包(本文中称为屏蔽包)来隐藏恶意包。如图8(a)所示,屏蔽包的安装脚本使用base64.b64decode()函数解码并执行无害的字符串“CODE REPLACE”。然而,在hener-1.0.0中,该字符串被编码的恶意负载替换。除了字符串之外,这些包完全相同。 henter-1.0.0 的逻辑很简单,但它在 PyPI 上存在了几天,直到我们发现它。一个可能的原因是,PyPI 的审计人员在看到屏蔽包时放松了警惕。
我们还检测到一些恶意软件包使用大量空格或空行将恶意负载隐藏在审核员可视区域之外。一个例子如图8(b)所示。在 ChromeSelenium-0.1.0 中,攻击者在无害指令“import os”后插入数百个空格,将恶意负载放置在 Python 编辑器窗口的可视区域之外。这种简单而狡猾的技术具有很强的欺骗性,甚至可能欺骗熟练的审计人员。
Discussion And Limitations
- Large Models 大型模型
近年来,已经提出了一些编程语言的大型模型。它们在代码克隆检测和代码搜索等任务中表现出了良好的性能。然而,训练这些模型需要大量的计算能力。例如,如中所述,训练UniXcoder模型需要4台DGX-2机器,每台机器有16个NVIDIA Tesla V100 32GB GPU。在许多实际场景中无法满足这种计算资源需求。即使微调大型模型也通常需要大量资源。本研究的目的是提供一种轻量级且快速的方法。事实上,我们的轻量级模型在目标问题上效果很好。我们方法的在线阶段甚至可以在笔记本电脑上运行。使用大型模型进行恶意安装脚本检测可能会过度武装。当然,用户也可以在负担得起的情况下构建或微调大型模型来进一步改进我们的方法。
- Potential Evasion 潜在的逃避
对于 MPHunter,攻击者可以上传多个相似但不完全相同的包,导致它们形成一个集群而不是异常值来逃避检测。例如,他们可以选择不同的功能来实施恶意行为。理论上,不可能完全识别所有恶意软件变种。然而,我们可以采用 APIEM 等模型来标准化脚本并标准化类似操作的表示,从而显着提高标准。此外,经验丰富的攻击者可能会将攻击负载分布在多个包中。为了有效地检测此类恶意包,需要进行包间分析,并构建全面的包库。这提出了一个具有挑战性的问题,值得未来进一步研究。
- Leveraging Only Malicious or Benign Samples 仅利用恶意或良性样本
一个自然的想法是直接测量新上传的安装脚本与已知恶意脚本之间的相似度来检测恶意 PyPI 包。我们进行了实验来验证这种方法的可行性。结果并不理想,召回率下降到40%(24/60),有10个检测到的样本排名甚至低于第100名。这表明采用聚类来识别异常值是必要的。此外,实验表明,仅使用良性集来识别异常值也是无效的,因为这样做会导致更差的召回率(30%,18/60)。
- Potential Enhancement 潜在的增强
MPHunter 本质上是一种基于统计的技术。在实践中,我们可以不断地将新确认的良性和恶意样本引入MPHunter数据集中,建立更坚实的统计基础来增强其性能。此外,目标脚本中还存在其他可利用的信息,例如变量名称、字符串常量和代码注释。理论上,整合更多信息有助于提高性能。此外,相对少量的恶意负载可以放置在模块初始化脚本中,即init.py。此类脚本也是潜在的聚类目标。我们可以将所提出的方法应用于它并发现更多恶意软件包。未来我们将开展相关研究。
- Other Repositories 其他存储库
还有其他流行的软件存储库,例如 npm 和 Rubygems。虽然检测其他存储库中的恶意包超出了本文的范围,但所提出的方法并非特定于 PyPI。事实上,npm 上的 JavaScript 包通常有一个安装脚本,这经常成为攻击的目标。此外,大多数恶意 npm 软件包 (64%) 在安装期间发起攻击。将 MPHunter 移植到其他存储库不存在重大技术挑战。
Related Work
- Software Supply Chain Attacks 软件供应链攻击
- Attack Detection 攻击检测
- Code Clone 代码克隆
Conclusion
在本文中,我们提出了 MPHunter,一种轻量级、无显式知识的恶意 PyPI 包检测方法。引入聚类技术对软件包的安装脚本进行分组,根据与良性和已知恶意样本的距离来识别异常值或可疑样本并进行排名。嵌入模型经过训练,可将目标脚本编码为向量,确保可扩展地执行聚类和排序。这样我们就可以有效、高效地利用MPHunter从大量的包中识别出恶意包。我们对 30 批新上传的 PyPI 包应用了 MPHunter,发现了 60 个未知恶意包。所有检测到的恶意软件包均已被确认为真正的恶意软件。此外,手动分析显示 MPHunter 在分析的包中识别出所有潜在的恶意安装脚本(100% 召回)。这表明 MPHunter 是 Python 社区可以采用的一种实用方法来检测新出现的恶意包。我们相信所提出的方法可以作为现有软件供应链安全分析技术的宝贵补充。我们计划将来将我们的方法移植到其他软件存储库,例如 npm。
References
56