开源生态系统因其广泛应用,已成为供应链攻击的主要目标。恶意行为者经常利用开源包的内置功能自动分发和执行有害代码。他们尤其青睐两种技术:安装包时自动执行的预安装脚本,以及看似无害但导入恶意依赖项的包。
随着这些策略变得越来越容易识别,当前的安全工具和警惕的开发人员已经能够更快地检测到它们。然而,一个经常被忽视但潜在危险的功能仍然存在:入口点。
本文探讨了攻击者如何利用多个编程生态系统(特别是 PyPI)中的入口点来诱使受害者运行恶意代码。虽然这种方法不像自动脚本或恶意依赖项那样可以立即破坏系统,但它为耐心的攻击者提供了一种更隐蔽的方式来渗透系统,可能会逃避标准安全措施的检测。
通过了解这个鲜为人知的攻击载体,我们可以更好地防御不断演变的开源供应链攻击。
要点
-
入口点是一种用于暴露包功能的强大特性,在各种生态系统中都容易被利用,包括 PyPI(Python)、npm(JavaScript)、Ruby Gems、NuGet(.NET)、Dart Pub 和 Rust Crates。 -
攻击者可以利用这些入口点在运行特定命令时执行恶意代码,这在开源领域构成了广泛的风险。 -
攻击方法包括指令劫持(冒充流行的第三方工具和系统命令)以及通过恶意插件和扩展针对开发过程的各个阶段。每种方法都有不同程度的潜在成功率和检测风险。 -
入口点攻击虽然需要用户交互,但为攻击者提供了一种更隐蔽和持久的系统攻击方法,可能会绕过传统的安全检查。 -
这种攻击载体对个人开发者和企业都构成风险,突显了需要更全面的 Python 包安全措施。
理解 Python 入口点
入口点是包系统的一个强大功能,它允许开发人员将特定功能作为 CLI 命令暴露出来,而无需用户了解包的确切导入路径或结构。
入口点有几个用途,包括:
-
创建用户安装包后可以运行的命令行脚本。 -
定义插件系统,让第三方包可以扩展核心包的功能。
最常见的入口点类型是 console_scripts,它指向你希望作为命令行工具提供给安装你的包的人使用的函数。
虽然主要是为了增强模块化和插件系统而设计的,但如果被滥用,入口点可能成为恶意行为者嵌入和执行有害代码的载体。要了解攻击者如何利用 Python 入口点,我们首先需要了解入口点最初是如何工作的。
包元数据中如何定义入口点
入口点定义的位置和格式可能因包格式(wheel 或源代码分发)而异。
源代码分发(.tar.gz)
对于源代码分发,入口点通常在包的设置配置中定义。这可以是传统设置中的 setup.py、setup.cfg,或更现代的打包方法中的 pyproject.toml。
以下是 setup.py 中定义入口点的示例:
from setuptools import setup
setup(
# ... other setup parameters ...
entry_points={
'console_scripts': [
'my_command=mypackage.module:my_function',
],
'my_package.plugins': [
'plugin_name=my_package.plugins:PluginClass',
],
},
)
Wheel 文件(.whl)
在 wheel 文件(一种已构建的包格式)中,入口点在 .dist-info 目录下的 entry_points.txt 文件中定义。
以下是上述示例的 entry_points.txt 文件可能的样子:
[console_scripts]
my_command = mypackage.module:my_function
[my_package.plugins]
plugin_name = my_package.plugins:PluginClass
入口点的语法遵循以下模式:
name = package.module:object
-
name: 入口点的名称(例如,控制台脚本的命令名称) -
package.module: Python 模块路径 -
object: 模块中要使用的对象(函数、类等)
在上面的示例中,my_command 是一个将在安装过程中创建的控制台脚本。安装包后,当用户在终端中输入 my_command 时,它将执行 mypackage.module 中的 my_function。
plugin_name 是一个自定义入口点,可以被 my_package 用来发现插件。它指向 my_package.plugins 中的 PluginClass。
当安装包时,这些入口点会被记录在包的元数据中。然后,其他包或工具可以查询这些元数据来发现和使用定义的入口点。
如果攻击者能够操纵合法包的元数据或说服用户安装恶意包,他们就可能在定义的命令或插件被调用时在用户的系统上执行任意代码。在接下来的部分中,我将提供多种攻击者可能用来诱使用户通过入口点执行恶意代码的方法。
理解操作系统中的 CLI 命令
命令行界面(CLI)命令是用户通过基于文本的界面与操作系统交互的主要方式。这些命令由 shell 解释和执行,shell 充当用户和操作系统之间的中介。
当用户输入命令时,shell 遵循特定的解析机制来定位和执行相应的程序。不同 shell 之间的确切顺序可能略有不同。然而,这个过程通常从按顺序检查 PATH 环境变量中列出的目录开始,并运行它找到的第一个匹配的可执行文件。用户可以通过在终端中输入"echo $PATH"命令(不同操作系统的确切命令可能不同)来查看当前的 PATH,这会显示 shell 搜索可执行文件的目录列表。
这个解析过程确保当用户输入命令时,会执行适当的操作。在考虑 Python 入口点(可以创建新的 CLI 命令)如何与现有系统命令交互或可能干扰它们时,理解这个过程至关重要。
攻击者如何滥用入口点执行恶意代码
恶意行为者可以通过多种方式利用 Python 入口点来诱使用户执行有害代码。我们将探讨一些策略,包括指令劫持、恶意插件和恶意扩展。
指令劫持
冒充流行的第三方命令
恶意包可以使用入口点来冒充广泛使用的第三方工具。这种策略对经常在工作流程中使用这些工具的开发人员特别有效。
例如,攻击者可能创建一个带有恶意'aws'入口点的包。当不知情的开发人员(经常使用 AWS 服务)安装这个包并后来执行 aws 命令时,假冒的'aws'命令可能会窃取他们的 AWS 访问密钥和密钥。这种攻击在 CI/CD 环境中可能特别具有破坏性,因为 AWS 凭证通常存储在那里以进行自动部署 - 可能会让攻击者访问整个云基础设施。
另一个例子可能是冒充'docker'命令的恶意包,针对使用容器化应用程序的开发人员。假冒的'docker'命令可能会在构建或部署期间秘密地将镜像或容器规范发送到攻击者的服务器。在微服务架构中,这可能会暴露敏感的服务配置,甚至导致专有容器镜像被窃取。
其他可能成为冒充目标的流行第三方命令包括但不限于:
-
npm (Node.js 包管理器) -
pip (Python 包安装程序) -
git (版本控制系统) -
kubectl (Kubernetes 命令行工具) -
terraform (基础设施即代码工具) -
gcloud (Google Cloud 命令行界面) -
heroku (Heroku 命令行界面) -
dotnet (.NET Core 的命令行界面)
这些命令在各种开发环境中广泛使用,使它们成为寻求最大化恶意包影响的攻击者的诱人目标。
冒充系统命令
通过使用常见系统命令名称作为入口点,攻击者可以冒充基本的系统工具。像'touch'、'curl'、'cd'、'ls'和'mkdir'等命令都可能被劫持,当用户试图使用这些基本工具时,可能会导致严重的安全漏洞。
虽然这种方法可能为攻击者提供了最高的机会让受害者意外执行恶意代码,但它也带来了最高的失败风险。这种方法的成功主要取决于 PATH 顺序。如果包含恶意入口点的目录在 PATH 中比系统目录出现得更早,那么将执行恶意命令而不是系统命令。这在本地包目录优先的开发环境中更可能发生。
另一点需要记住的是,全局安装的包(需要 root/admin 权限)可能会覆盖所有用户的系统命令,而用户安装的包只会影响该特定用户的环境。
通过命令包装增强攻击
在每种指令劫持策略中,虽然攻击者只是简单地覆盖 CLI 命令会更容易,但保持不被发现的机会相当低。一旦受害者无法执行命令,他们很可能立即产生怀疑。然而,通过一种称为"命令包装"的技术,这些攻击可以变得更加有效和隐蔽。命令包装不是简单地替换命令,而是创建一个作为原始命令包装器的入口点。它的工作原理如下:
-
当用户调用命令时(无论是冒充的第三方工具还是试图冒充系统命令),触发恶意入口点。 -
除了悄悄执行攻击者的恶意代码外,它还会使用用户的所有参数调用原始的合法命令。 -
最后,它将合法命令的输出和退出代码返回给用户。
这种命令包装方法特别危险,因为它在用户不知情的情况下执行恶意代码,同时保持正常操作的外观。由于合法命令仍然运行,其输出和行为得以保留,因此通过正常使用没有立即的入侵迹象,使攻击极难被检测到。这种隐蔽的方法允许攻击者保持长期访问并可能窃取敏感信息而不引起怀疑。
然而,实施命令包装需要攻击者进行额外的研究。他们需要了解不同操作系统上目标命令的正确路径,并考虑到他们代码中可能出现的错误。这种复杂性随着攻击目标系统的多样性而增加。
根据被劫持的命令,另一种方法是恶意包不仅执行其隐蔽操作,还复制原始命令的部分或全部功能。包装器不调用真实命令,而是模拟其行为。这种方法可能会进一步降低怀疑,特别是对于较简单的命令,但它需要攻击者付出更多努力来准确模仿原始命令在各种场景下的行为。
这些攻击的成功最终取决于恶意包被安装,并且其脚本目录在系统的 PATH 中被优先考虑。
恶意插件和扩展
滥用入口点的另一种强大技术是为流行的 Python 工具和框架创建恶意插件。这种方法特别危险,因为它针对的是开发和测试过程本身。
操纵 pytest
作为一个例子,让我们考虑攻击者如何针对 pytest,这是 Python 生态系统中广泛使用的测试框架。通过创建一个恶意的 pytest 插件,攻击者可能会破坏整个测试过程的完整性。
这样的攻击可能是这样工作的:
-
攻击者创建一个使用 pytest 的入口点系统注入恶意代码的插件。 -
这个插件被作为一个看似有用的测试工具分发。 -
一旦安装,该插件可以操纵测试过程的各个方面,如断言处理。
然后,恶意插件可以在测试过程中悄悄地在后台运行恶意代码。恶意插件还可以覆盖 pytest 的断言比较,导致例如所有相等性检查都通过,而不管它们的实际值如何,从而导致测试结果出现误报,允许有漏洞或存在漏洞的代码通过质量检查而不被注意到。
操纵 Flake8
攻击者还可以针对流行的开发工具,操纵它们以运行恶意扩展。Flake8 是 Python 生态系统中广泛使用的代码检查工具,就是一个例子。由于 Flake8 使用入口点来发现和加载扩展,因此它成为恶意行为者的潜在目标。
攻击者可能会通过创建一个伪装成有用代码检查规则的恶意扩展来利用 Flake8。这个扩展会在包的设置配置中定义为一个入口点。例如,设置文件可能会指定一个名为 'MCH' 的入口点,指向包内的恶意检查器类。
图 6
恶意检查器的实现可能包括在受害者的系统上执行有害操作、注入恶意“修复”代码或操纵代码检查结果以隐藏或创建问题的功能。当用户在其代码库上运行 Flake8 时,这个恶意扩展会被激活,允许攻击者执行其有害代码。
图 7
这种攻击特别危险,因为代码检查工具通常在整个代码库上运行,这给了攻击者广泛的访问权限。此外,攻击可以通过看似有用的代码检查规则进行,使其不太可能引起怀疑。它可以作为更大规模供应链攻击的一部分,用于收集情报或在目标代码库中引入漏洞。
绕过 .whl 文件限制
Python wheel(**.whl** 文件)因其在包安装中的性能优势而变得越来越普遍。然而,它们对攻击者提出了独特的挑战。
尽管 .tar.gz 和 .whl 文件都可能包含 setup.py 文件,但 .whl 文件在安装过程中不会执行 setup.py。这一特性传统上使得攻击者在使用 .whl 文件时难以在安装过程中实现任意代码执行。
然而,我们讨论的入口点攻击方法为这一限制提供了变通方案。通过操纵入口点,攻击者可以确保在特定命令运行时执行他们的代码,即使包是以 .whl 文件形式分发的。这一点尤其重要,因为当开发者使用类似“pip -m build”的命令构建 Python 包时,较新的 pip 版本会自动创建 .tar.gz 和 .whl 文件。此外,pip 在安装时优先交付 .whl 文件。
这种包格式和安装行为的变化为攻击者提供了新的机会。许多安全工具专注于分析安装过程中预安装脚本的执行,这些脚本通常与 .tar.gz 文件相关。因此,它们可能会错过以 .whl 文件形式分发的包中的恶意代码,特别是当恶意行为是通过入口点触发而不是立即执行时。
其他生态系统中的入口点
虽然本文主要关注 Python,但入口点被用于恶意目的的漏洞并不仅限于 Python 生态系统。通过我们的研究,我们已经确认这种类型的攻击向量存在于其他几个主要生态系统中,包括:
npm(JavaScript)、Ruby Gems、NuGet(.NET)、Dart Pub 和 Rust Crates,尽管漏洞可能不仅限于这些。
了解各种编程语言和包管理器中入口点的工作原理对于理解这种潜在安全风险的广泛性质以及制定全面的防御策略至关重要。
结论
入口点虽然是合法包开发的强大和有用功能,但也可能被操纵以跨多个编程生态系统传递恶意代码。
攻击者可以通过多种方法利用这一机制,包括命令劫持和为流行开发工具创建恶意插件和扩展。
展望未来,开发全面的安全措施以应对入口点利用至关重要。通过理解和解决这些风险,我们可以努力创造一个更安全的 Python 包环境,保护个人开发者和企业系统免受复杂的供应链攻击。
作为 Checkmarx 供应链安全解决方案的一部分,我们的研究团队持续监控开源软件生态系统中的可疑活动。我们跟踪并标记可能表明不法行为的“信号”,包括可疑的入口点,并及时通知客户,帮助他们防范潜在威胁。
原文始发于微信公众号(独眼情报):新的供应链攻击技术可以木马化所有命令行命令
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论