0x01 前言
在一次授權測試中對某網站進行測試時,marry大佬發現了一個網站的備份文件,里面有網站源代碼和數據庫備份等。根據網站信息和代碼都可以發現該系統采用的是微擎cms,利用數據庫備份中的用戶信息解密后可以登錄系統,接下來要看是否可以獲取webshell。
0x02 WEBSHELL獲取的嘗試
有了數據庫備份文件,然后找一下是否有用戶的信息,能否登錄系統。
1.登錄后台
解壓備份文件可以從data/backup
目錄下找到數據庫的備份,從中找到了用戶表ims_users
。
知道了用戶名、加密后的密碼和salt,我們去看一下密碼加密的算法。
我這里直接搜索password
,在forget.ctrl.php中找到了一處。
密碼加密方法是$password = md5($password . $member_info['salt'] . $_W['config']['setting']['authkey']);
。是根據原密碼+salt+authkey
的形式進行拼接,然后進行md5加密。
authkey在data/config.php
文件中。
現在salt和authkey以及加密后的密碼已經獲得,開始去解密密碼是多少。這里我們將salt
和authkey
拼接為新的salt
,然后使用md5($pass.$salt)
的加密方式進行解密。
解密后即可登錄后台。
接下來就是webshell的獲取了。
本以為都已經是管理員了,獲取shell就是分分鍾的事,然而事情遠遠沒有那么簡單。
2.失敗的獲取shell過程
根據搜索發現,該cms后台獲取shell的方法也不少,主要還是圍繞執行sql這里。但我這里都失敗了,就簡單的提一下。
第一種方法:
站點管理-附件設置-圖片附件設置-支持文件后綴,任意添加一個類型,例如添加pppppp
。
然后執行sql語句
UPDATE ims_core_settings SET value = replace(value, 'pppppp', 'php ')
更新緩存,之后就可以上傳"*.php "
文件了。但是有限制,適用於apache下,而且版本有限制。目標站不使用該方法的原因有二,一是該系統上傳的位置是騰訊雲COS上,二是server是Tengine。
第二種方法:
第二種方法也是和sql執行有關,利用日志文件寫shell。
show variables like '%general%'; #查看配置 set global general_log = on; #開啟general log模式 set global general_log_file = '/var/www/html/1.php'; #設置日志目錄為shell地址 select '<?php eval($_POST[cmd]);?>' #寫入shell
或者通過慢查詢(slow_query_log)的方法寫shell。但目標系統也是失敗,執行sql的時候報錯。
還有一些其他的方法,這里測試也是失敗的,就不再列舉了。
0x03 代碼審計
病急亂投醫,熬成老中醫。既然之前的方法不管用,只好去翻代碼吧,找找是否有新的利用方式。翻出之前的一個文檔,從里面找到之前的審計過程,看能否對現在有用。結果打開發現只有一個數據包和還有一句未實現的結論。
沒辦法,只好重新圍着這個點繼續審計,看是否能有所進展。
1.分析
打開文件web/source/cloud/dock.ctrl.php
,找到執行的download
方法。
代碼比較簡單,我大概說一下這里的流程:
如果請求包非Base64加密的格式,那么$data
就是請求包的內容。然后對$data
進行發序列化返回$ret
,接下來獲取$ret['file']
並Base64解密返回$file
。當存在gzcompress
和gzuncompress
這兩個函數時,就會利用gzuncompress
函數對$file
進行解壓操作。
將獲取的$file
進行md5加密后,與$ret['path']
以及獲取的$_W['setting']['site']['token']
進行拼接為$string
。當滿足$_W['setting']['site']['token']
非空並且$string
md5加密后的結果與$ret['sign']
一致時,才可以進行下面的操作。下面就是文件的寫入了,根據$ret['path']
進行判斷,然后寫入的位置不一樣。
這里關鍵的一點就是$_W['setting']['site']['token']
這個值的獲取。這個是利用authcode函數對cache_load(cache_system_key('cloud_transtoken'))
進行解密獲取的。
authcode
函數位於framework/function/global.func.php
文件中。
由上面代碼可以看出,要想使用authcode
加解密,需要知道$GLOBALS['_W']['config']['setting']['authkey']
,在上面提到過,authkey在data/config.php
文件中。
那么如果想任意寫文件,就需要知道cache_system_key('cloud_transtoken')
的內容了。
2.cloud_transtoken的獲取
通過搜索發現,這個值是在文件framework/model/cloud.mod.php
中的cloud_build_transtoken
函數中被寫入的,通過進入cache_write
方法,發現會寫入數據庫中。
既然會寫入到數據庫中,而且目標系統下載到時候有數據庫的備份文件,我們直接在數據庫備份文件中搜索cloud_transtoken
。結果並沒有找到,可能原因是沒有寫入cloud_transtoken
的時候就進行了數據庫備份。
我們往上回溯,看哪里調用了cloud_build_transtoken
。
發現了其中的一條利用鏈:
當訪問http://ip:port/web/index.php?c=cloud&a=profile 時,就會判斷站點ID和通信密鑰是否為空(即站點是否注冊),如果站點注冊了,就會調用cloud_site_info()
函數獲取站點信息。函數cloud_site_info()
調用了cloud_api('site/info')
,這里的method為site/info
,所以繼續調用cloud_build_transtoken
從會而將cloud_transtoken
的內容寫入數據庫。然后通過數據庫備份的功能,就可以看到數據庫中保存的cloud_transtoken
,進而可以利用之前的分析寫shell。
3.自定義數據庫備份
由於數據庫備份需要關閉站點,為了不影響目標站點的使用,這里我們搭建一個環境演示一下過程(需要注冊站點)。
登錄成功后更新緩存,然后訪問http://ip:port/web/index.php?c=cloud&a=profile ,關閉站點后進行數據庫備份。
發現可以獲取cloud_transtoken
,但是數據庫目錄和文件的名字是隨機的。
而且如果備份文件里面的數據庫文件不是最新的,那么即使獲取到cloud_transtoken
也無法利用,我們需要最新的備份文件。
然后我們看一下數據庫備份是怎么實現的,打開web/source/system/database.ctrl.php
。
發現文件夾和分卷名可以自定義,如果為空或不滿足條件的話,文件夾是時間戳、下划線和8位隨機字符串的拼接,分卷名是volume-10位隨機字符串-1.sql
的形式,既然可以自定義,那么就簡單多了。
訪問鏈接http://ip:port/web/index.php?c=system&a=database&do=backup&status=1&start=2&folder_suffix=123&volume_suffix=456 進行數據庫備份,則數據庫備份文件的地址為:http://ip:port/data/backup/123/volume-456-1.sql
然后就可以隨時獲取cloud_transtoken
了。接下來就可以進行shell的獲取了。
4.獲取WEBSHELL
根據上面的分析,cloud_transtoken
、authkey
已經知道了,接下來就是構造payload了。
然后請求http://ip:port/web/index.php?c=cloud&a=dock&do=download ,data為生成的payload。
可以進行任意文件的寫入,對目標系統進行測試,也成功獲取了shell。
5.延伸
上面是因為有系統文件備份,然后獲取/data/config.php
中的authkey
。如果沒有文件備份,登錄了一個管理員權限的用戶,能否獲取shell呢。答案也是可以的。
該系統有一個木馬查殺功能,可以根據這個功能讀取文件內容。
選擇一個目錄,然后提交並攔截數據庫包,修改查殺目錄為data/.
,特征函數為password
。然后就可以看到查殺結果,獲取authkey
的值。
在對最新版 v2.5.7(202002140001)進行木馬查殺的時候,可以從查殺報告中看到該文件,但是查看時提示文件不存在。原因是最新版利用正則對文件路徑進行匹配,如果匹配成功就提示文件不存在(windows下可以利用大寫路徑繞過)。
0x04 總結
根據上面對分析過程,該漏洞的利用過程如下:
1.成功登錄后台,且擁有管理員權限。
2.更新緩存(非必須),訪問鏈接http://ip:port/web/index.php?c=cloud&a=profile 寫入cloud_transtoken
到數據庫中。
3.關閉站點並進行使用自定義的目錄進行數據庫備份,鏈接地址:http://ip:port/web/index.php?c=system&a=database&do=backup&status=1&start=2&folder_suffix=123&volume_suffix=456 。然后下載數據庫備份,地址為:http://ip:port/data/backup/123/volume-456-1.sql (多個分卷的話文件名為volume-456-2.sql、volume-456-3.sql... ),然后找到cloud_transtoken
。
4.生成payload,請求http://ip:port/web/index.php?c=cloud&a=dock&do=download ,寫入shell。
總的來說,利用上述方法獲取shell需要滿足兩個條件,第一是擁有一個管理員權限的用戶,第二就是該站點注冊了雲服務。