一步一步教你漏洞挖掘之python Flask框架pgadmin4 組合RCE漏洞鏈完整分析
引言
pgadmin4是一款為PostgreSQL設計的可靠和全面的數據庫設計和管理軟件, 它允許您連接到特定的數據庫,創建表和運行各種從簡單到復雜的SQL語句。
2021年初看到有大佬發了個關于pgadmin4的RCE漏洞。當時很感興趣跟蹤調試了下,發現這個漏洞挺有意思,通過一系列的組合模式,最終構建了一條完整的RCE漏洞鏈條。整個分析過程相對比較復雜,下面將完整分析過程分享給大家。
環境安裝
可以通過docker拉取進行安裝,也可以手動下載后自行安裝,既然想完整研究該漏洞,選擇手動安裝方式。因為使用的是python2 ,所以這里安裝最后支持python2的版本v4.21:

分析代碼結構發現系統是基于Flask框架進行構建的。在啟動前可以修改配置文件,將服務器IP修改為`0.0.0.0`,這樣啟動后就可以外部訪問了,啟動輸入郵箱和密碼`test@123.com/123456`。訪問首頁:

遠程調試
利用pycharm進行遠程調試。配置sftp:


配置python解析器:

嘗試1:認證缺陷
正常的登錄認證信息為`test@123.com/123456`,但是在黑盒測試過程中發現輸入`1/123456`也可以登錄成功。深入了解一下登錄后賬號驗證的邏輯。在Flask項目登錄代碼處下斷點:

跟下來,最后通過`flask_security`的`forms.py`中的函數`validate()`進行驗證:

其中的`self.email.data`就是提交的參數1。繼續跟進`get_user`:

參數`identifier`對應的就是`email`的值。當為數字或者`uuid`時,調用如下查詢過程:
rv = self.user_model.query.get(identifier)if rv is not None: return rv

也就是說`flask_security`存在邏輯缺陷,可以在不知道郵箱地址的情況下,通過主鍵`id`對密碼進行爆破。這就導致了我們只要知道密碼,可以通過自增爆破`id`實現認證繞過。如果結合一定實際數據資料,很容易構造密碼字典進行爆破。
嘗試2:路徑穿越嘗試(失敗)
認證后在"新增服務器"功能中,發現有一個證書模塊,通過`select file`界面點擊刷新會列出目錄文件:

數據包如下:

嘗試路徑穿越:

分析代碼,對應的路由映射如下:

主要由位于`Filemanger`中的`getfolder`函數處理:

最終會進入`list_filesystem`函數:

函數`check_access_permission`如下:

`check_access_permission`函數會將`path`參數值與資源目錄合并,并使用`os.path.abspath`函數獲取真實路徑,最終被還原的真實路徑必須要包含資源路徑,這樣無論我們修改任何跨目錄格式最終都不會和原有資源路徑匹配造成異常拋出:

所以這里無法進行路徑穿越。
嘗試3:路徑穿越嘗試(成功)
發現`select file`界面還有一個文件上傳的功能:

選擇一個文件上傳,發現回顯的包中的路徑中包含了用戶名信息:

定位`add`函數:

查看`self.dir`的來源:


程序中使用`os.path.join`將存儲目錄與`username`進行拼接生成新的目錄。我們知道`os.path.join`函數存在一個邏輯問題:只要最后一個參數為`/`開頭就會忽略之前所有參數然后返回路徑:

這樣可以嘗試創建一個新用戶(需要管理員權限,也就是`id=1`),`username`改為`/`,既可以遍歷到根目錄,又可以通過`check_access_permission`的檢查。分析添加用戶操作:

發現前端是無法編輯用戶名的,發送數據包如下:

傳遞的參數中沒有用戶名的相關信息,定位后端處理代碼:

函數`create_user`:

如果我們傳入`username`參數,同時確保`auth_source`的值不是`internal`,那么就給`usr`變量加入`username`。回到`create`函數:

可以控制`username`變量并寫入數據庫:

現在我們登出,并用剛才注冊的新用戶登錄系統,回到證書模塊的`select file`界面:

實現了任意文件遍歷。
嘗試4:任意文件下載
看下`Filemanager`類的定義:

相當于通過`mode=getfolder`實現了目錄遍歷,可以嘗試進一步實現文件下載。先關注下上面請求中的URL:

分析后發現這個數字來源于點擊的時候發送的請求:

分析代碼:

函數`create_new_transaction`:

根據`dialog_type`的取值賦予不同的操作權限,關注下`storage_dialog`,具有`download`權限。所以改下發送的數據包:


嘗試5:sqlite反序列化漏洞
pgAdmin采用sqlite數據庫存儲認證信息:

數據庫路徑為`/var/lib/pgadmin/pgadmin4.db`,實際部署環境中可利用上面的目錄遍歷來尋找數據庫路徑。
通過上面的分析,具備下載`pgadmin4.db`的條件,也可以上傳`pgadmin4.db`。那么可以考慮在讀寫數據庫代碼中尋找一個反序列化操作的點,然后下載`pgadmin4.db`,修改對應數據表字段值,然后上傳惡意`pgadmin4.db`并觸發數據庫讀寫操作,這樣可以達到RCE的目的。
首先找一個存在反序列化過程的數據庫操作點,通過搜索分析(pycharm搜索有點問題,可能漏掉一些點,建議用sublime):

先看下函數`_retrieve_process`的調用情況:

上面的請求就可以觸發反序列化操作。下載`/var/lib/pgadmin/pgadmin4.db`:

`process`數據表定義:

手動添加一條記錄:

構造一個反彈shell的payload:


上傳文件:

訪問如下接口觸發RCE:


執行`payload.py`需要注意編譯環境(windows和Linux操作是不一樣的),否則可能異常:

主要原因是由于windows和Linux生成的序列化數據頭不一樣,從而導致異常。
總結
上面分析了多個漏洞或缺陷,主要包括:
- 認證機制缺陷:這個主要是Flask_security本身的問題,導致無需知道注冊郵箱地址,完成認證信息爆破
- 路徑穿越漏洞:充分利用python語言一些特性,比如os.path.join在路徑拼接的特性,引發了路徑穿越的風險
- 反序列化漏洞:sqlite數據庫是文件型數據庫,通常可考慮替換配合反序列化漏洞出發操作
組合起來構造了一條完整RCE漏洞鏈條,整個過程比較繞,但是認真分析下來感覺挺有意思。