Package 釣魚

0x00 前言

前幾天 Samba 公開了一個遠程代碼執行的漏洞,然后各種 POC 也隨之出現, exploit-db 上也有這樣一個 Python 版本的 POC: Samba 3.5.0 – Remote Code Execution.

直接執行 POC,報錯誤信息:

Package 釣魚

這種情況非常簡單,直接 pip install smb 就行,但是:

Package 釣魚

好吧,我們還是需要 Google 一下這個 smb 的 package 名字,最后發現原來是 pysmb

Package 釣魚

最后 POC 終于跑了起來.

我們再回過頭來看看整個流程,似乎并沒有什么地方不對勁。

直接說問題所在吧,如果你在 2017-05-242017-05-31 這段時間內執行過 pip install smb 或者pip download smb, 那么恭喜你,你的名字可能出現在我的綿羊墻上。

0x01 試水 (2017-05-23 19:00)

第一天,我在 PyPI 上投放了 4 個 evil package: python-devmongodbproxyshadowsock 測試一下不檢查 package、隨意安裝 package 的人有多少。

其中所有的內容都是用 cookiecutter 根據模版 cookiecutter-evilpy-package 生成。

每個 package 都會收集用戶的

  • username
  • hostname
  • ip
  • hostinfo

我選擇了 GitHub Issues + webtask.io 的方式,將安裝 evil package 的用戶信息通過 webtask.io 中轉到 GitHub Issues 上對外公開。

所以我就在 Github 上注冊了個小馬甲 evilpackage 專門提交 Issue。

因為 webtask.io 獲取客戶端 ip 的時候,其實獲取到的是 webtask.io 前面 nginx 的 ip 地址,并不是用戶的 ip,所以就只能在代碼里面獲取客戶端的外網 ip. 使用 webtask.io 和 GitHub Issues 的主要原因是這兩都是免費的。

0x02 增加投放 package (2017-05-24 19:00)

查看了一天的 Issues 數量,大概有 700+,效果非常不錯,決定繼續投放 evil package。 與此同時,@ztz同學也加入了游戲,也在 RubyGems 上投放 Gems。

繼續投放 evil package,就必須想一些比較好的名字,我主要使用下面兩種方法:

  1. Google 搜索提示框
    直接根據 Google 的搜索框提示:Package 釣魚便收集到了沒有在 PyPI 上注冊,而且比較流行的 Package 名字:

    • caffe
    • ffmpeg
    • git
    • mkl
    • opencl
    • opencv
    • openssl
    • pygpu
    • tkinter
    • vtk
    • proxy
  2. 想象力
    依據平時寫代碼的經驗總結出下面可能覺得會常用,但并沒有在 PyPI 上注冊的 Package 名字:

    • ftp
    • smb
    • hbase
    • samba
    • rabbitmq
    • zookeeper
    • phantomjs
    • memcached
    • requirement.txt
    • requirements.txt

其中 requirements.txt 并沒有注冊成功,稍后再說。

0x03 暫停服務 (2017-05-25 23:00)

晚上回家的時候又統計了一下安裝量,一天安裝量達到了 2000+,效果已經很顯著,不必再增加新的 package 了,但是到了晚上 23:00 的時候, 我的 GitHub Issues 被惡意插入臟數據,所以只能暫停服務:

Package 釣魚

之所以只能暫停服務,那是因為 webtask.io 沒法獲取客戶端 ip,我也沒法 ban 掉對應的 ip,作出任何相對的處理,只能停服務。

話說到底誰才是攻擊者。

0x04 evilpackage 被封 (2017-05-26 2:00)

我專門提交 Issue 的小馬甲 evilpackage 因為觸發了 GitHub 對 Spam 的檢測,所以被封號了。 早上起床看到消息后,立馬寫郵件申訴,直到 2017-05-26 13:00 終于回復我的郵件了:

Package 釣魚

0x05 放棄 webtask.io (2017-05-26 19:00)

為了避免和之前一樣被惡意插入臟數據,決定要放棄 webtask.io,每月花費 $10 巨款購入一臺 vps。

使用 nginx + flask 的配置,繼續將 user data 提交到 GitHub Issues 上。

nginx 的 ngx_http_limit_req_module 模塊最大能夠支持 1s/m,也就是最多可以限制每個 ip 在每分鐘內最多請求一次, 所以我們必須修改 ngx_http_limit_req_module 模塊代碼


 

增加一個 else if block,直接將 scale 增加到 1000,這樣就能限制每個 ip 在 16 min 內只能訪問一次我們的接口, 除非使用大量代理,不然很難在短時間內插入大量臟數據。

0x06 repo 被封 (2017-05-27 3:00)

早上起床刷新一下 GitHub Issues 頁面,結果發現:

Package 釣魚

郵件:

Package 釣魚

趕緊先上服務器加上一行代碼,將用戶上傳的數據先暫時存在本地(之前太懶)。 然后馬上回郵件,問情況,兩天后:

Package 釣魚

解封無望,之前的數據大概就是沒了。

目前還能通過 GitHub Search 找到以前的部分數據 GitHub Issue

0x07 寫 web 界面 (2017-05-30 19:00):

由于之前一直在忙,最后拖到了30號才開始寫 web 展示界面 http://evilpackage.fatezero.org/

也準備好新的 cookiecutter 模版 cookiecutter-evilpy-package

新的 cookiecutter 模版會提示用戶安裝了 evilpackage,并打開用戶的瀏覽器去訪問 http://evilpackage.fatezero.org/,讓用戶知道,自己已經是綿羊墻上的一員了。

計劃打算第二天再往 PyPI 上提交新版本的 Package。

0x08 清空 (2017-05-31):

早上查找資料的時候發現,原來已經有好幾批人干過和我一樣類似的事情了

前兩批都只是上傳一個 package 用來提示安裝用戶,也防止惡意用戶使用這些 package 名字, 后面一個小哥和我一樣收集了用戶不太敏感的信息,只不過他的數據一直沒有公開。

過了一會 @ztz 同學告訴我他的 RubyGems 被清空了。

再過了一會我這邊也被 PyPI 管理員警告要刪除賬號了,所以我就把所有的 Package 給刪除了,賬號也給刪除了。

目前為止所有的 package 又回到了 unregister 的狀態, 任何人都可以繼續注冊使用我之前注冊的 package.

0x09 數據統計

目前我只能對在 http://evilpackage.fatezero.org/ 上那 10685 條數據進行統計

從 2017-05-27 10:38:03 到 2017-05-31 18:24:07,總計 106 個小時內, 有 9726 不重復的 ip 安裝了 evil package,平均每個小時有 91 個 ip 安裝了 evil package。

  1. 每個 package 命中排名:
    2862 opencv
    2834 tkinter
    810 mkl
    789 python-dev
    713 git
    683 openssl
    535 caffe
    328 ffmpeg
    224 phantomjs
    200 smb
    191 vtk
    179 pygpu
    113 mongodb
    70 requirement.txt
    56 memcached
    31 rabbitmq
    15 ftp
    14 shadowsock
    12 samba
    10 proxy
    10 hbase
    5 zookeeper
  2. 前 50 個國家命中排名
    2507 United States
    1667 China
    772 India
    481 Germany
    448 Japan
    331 France
    319 Republic of Korea
    306 United Kingdom
    305 Russia
    297 Canada
    225 Brazil
    183 Australia
    179 Netherlands
    167 Poland
    147 Taiwan
    129 Italy
    127 Israel
    126 Spain
    106 Singapore
    103 Ukraine
    89 Hong Kong
    87 Switzerland
    76 Sweden
    74 Turkey
    60 Ireland
    57 Vietnam
    57 Iran
    54 Belgium
    53 Finland
    52 Austria
    49 Pakistan
    49 Indonesia
    47 Argentina
    43 New Zealand
    42 Mexico
    41 Romania
    40 Thailand
    37 Norway
    37 Czechia
    31 South Africa
    31 Denmark
    31 Colombia
    29 Portugal
    29 Greece
    29 Chile
    24 Philippines
    23 Malaysia
    20 Hungary
    20 Belarus
    19 Nepal
  3. 每個訪問排名
    28 114.255.40.3
    25 46.105.249.70
    16 54.84.16.79
    16 54.237.234.187
    16 54.157.41.7
    16 54.145.106.255
    16 52.90.178.211
    13 34.198.151.69
    12 52.221.7.193
    11 54.235.37.25
    10 34.224.47.129
    9 172.56.26.43
    7 94.153.230.50
    7 80.239.169.204
    7 73.78.62.6
    7 54.87.185.66
    7 52.207.13.234
    7 113.140.11.125
    6 52.55.104.10
    6 24.108.0.220

光從這幾天來看,在 PyPI 上投放 evilpackage 的效果還是非常不錯的, 每天都會有大概 2200+ 個獨立 ip 進行訪問,數據量稍微比之前那位小哥好一點, 也就是說,即便是類似的文章發出來,過了一年之后,隨意安裝 package 的這種情況完全沒有改善,可能更嚴重了。

那位小哥釋放掉所有的 package 之后,我作為一個 “惡意者” 再次使用他之前使用的 gitopenssl 名字來統計數據, 我作為一個 “惡意者”,被官方勒令刪除所有的 package,這些 package 名字再次被釋放,我比較好奇下一位 “惡意者” 會是誰, 會在 package 里放什么?會是和我一樣收集數據,還是直接rm -rf /,還是勒索。拭目以待。

0x10 requirements.txt

一般經常使用 Python 的人都知道 requirements.txt 是整個項目的依賴文件,一般這樣使用:

pip install -r requirements.txt

不過也有可能一時手速過快,敲打成

pip install requirements.txt

所以 requirements.txt 也是一個比較好的 evil package 的名字

詭異的 requirements.txt

在 2017-05-24 19:00 晚上,我嘗試在 PyPI 注冊上傳 requirements.txt 的時候:

Package 釣魚

嗯,都失敗了,但是 GitHub Issues 上竟然會有 153 個和 requirements.txt 相關的 Issues:

Package 釣魚

我并不懷疑這些 requirements.txt 數據的真實性,因為就沒有人知道我嘗試上傳過requirements.txt,所以這些數據肯定是真實的。

PyPI 上也并不存在 requirements.txt 信息,本地嘗試安裝也失敗了,至今仍未明白這種情況為何發生。

繞過 PyPI requirements.txt 的限制

在 PyPI 賬號被刪除之后,我還是對 requirements.txt 很好奇,為什么之前 GitHub 上會有記錄? 能不能繞過 PyPI 的限制?下面簡單講一下如何繞過 PyPI 的限制。

我們直接查看提交 Package 時,PyPI 對 Package 名字限制的地方:


 

通過上面的代碼,我們可以看到 PyPI 直接硬編碼 'requirements.txt', 'rrequirements.txt', 'requirements-txt', 'rrequirements-txt' 禁止用戶上傳這些文件。

我們再看看 pip install xxx 的時候,PyPI 是怎么查找 Package 的:


 

好吧,直接查找數據庫,我們再跟下來看 normalize_pep426_name


 

看到中間那個正則了吧,這也就意味著

pip install youtube-dl
pip install youtube_dl
pip install youtube.dl
pip install youtube-_-dl
pip install youtube.-.dl

這幾條命令其實都是等價的,都是在安裝 youtube_dl, 那么我們就可以很容易的就繞過 PyPI 的限制, 直接上傳一個 requiremnets--txt

twine register dist/requirements–txt-0.1.0.tar.gz
twine upload dist/requirements–txt-0.1.0.tar.gz

來來來,我們直接嘗試 pip install requirements.txt

Package 釣魚

通過上面的圖,我們可以看到 PyPI 已經返回我們的 package url, 到了 pip 準備安裝這個 package 的時候報錯了,所以直接看 pip 代碼:

 

看了代碼,也就是沒法在 url 中獲取 package 的版本號, 因為 package 的名字(requirements--txt)和搜索名字(requirements.txt)對不上,我們得找找其他方法:


 

看到這里,大家應該也知道了,之前我們一直都是使用 source 的方式提交 package,如果我們直接打包成 wheel, 根據上面的代碼,就不會再報錯了,我們重新打包,再次上傳:

Package 釣魚

終于成功了,當然 wheel 安裝方式并不能直接執行命令, 不過我們可以通過給 requirements.txt 添加一個惡意依賴達到執行任意代碼的效果。

在這里,我就添加了一個名為 ztz 的 source package,用于提醒安裝 requirements.txt 的用戶

Package 釣魚

0x11 總結

想做一件壞事情真不容易,快去看看 http://evilpackage.fatezero.org/ 上面有沒有你的名字。

 

原文作者:fate0