作者:niexinming @ n0tr00t security team
來源:http://www.n0tr00t.com
關于漏洞
這個漏洞比較有趣,寫出來給大家分享一下
這個漏洞影響的版本有ranzhi協同oa<=4.6.1(包含專業版)還有喧喧及時聊天系統<=1.3
出問題的地方是喧喧聊天系統,由于然之開源版和專業版自4.0之后都自帶這個聊天系統,所以都會被影響
從官網下周然之4.6.1之后首先看ranzhi\www\xuanxuan.php,這個文件是喧喧的入口,加載的模塊在ranzhi\framework\xuanxuan.class.php,由于聊天信息是用aes加密過的,初始的密鑰是88888888888888888888888888888888,我相信沒有幾個人會去改的吧,所以漏洞一開始就已經埋下來了
再往下看,看到118行的parseRequest這個函數,看看這個系統是怎么處理傳遞進來的參數的
public function parseRequest()
{
$input = file_get_contents("php://input");
$input = $this->decrypt($input);
$userID = !empty($input->userID) ? $input->userID : '';
$module = !empty($input->module) ? $input->module : '';
$method = !empty($input->method) ? $input->method : '';
$params = !empty($input->params) ? $input->params : array();
if(!$module or !$method or $module != 'chat')
{
$data = new stdclass();
$data->module = 'chat';
$data->method = 'kickoff';
$data->data = 'Illegal Requset.';
die($this->encrypt($data));
}
if($module == 'chat' && $method == 'login' && is_array($params))
{
/* params[0] is the server name. */
unset($params[0]);
}
if($userID && is_array($params))
{
$params[] = $userID;
}
$this->setModuleName($module);
$this->setMethodName($method);
$this->setParams($params);
$this->setControlFile();
}
首先,從原始post數據獲取數據,解密,獲取userID,module,method,params這幾個參數,其中userID的用戶id,module是調用模塊,method是調用的方法,params是傳遞的參數,這里有一個限制,模塊只能加載chat里面的,也就是只能加載和調用ranzhi\app\sys\chat\control.php這里面的函數,由于調用的函數名可以控制,其實可以調用繼承的父類種函數,對,這個漏洞最關鍵一點是可以調用父類函數,看一下,這個chat類繼承于control
class chat extends control
control類在ranzhi\framework\control.class.php,可以看到這個類里面只有一個函數就是fetch函數,但是這個類又繼承了baseControl這個類,但是已經不重要了,用這個函數就可以了
這個函數在前面檢查模塊是否存在之后就把參數放入call_user_func_array中了
public function fetch($moduleName = '', $methodName = '', $params = array(), $appName = '')
{
if($moduleName == '') $moduleName = $this->moduleName;
if($methodName == '') $methodName = $this->methodName;
if($appName == '') $appName = $this->appName;
if($moduleName == $this->moduleName and $methodName == $this->methodName)
{
$this->parse($moduleName, $methodName);
return $this->output;
}
$currentPWD = getcwd();
/**
* 設置引用的文件和路徑。
* Set the pathes and files to included.
**/
$modulePath = $this->app->getModulePath($appName, $moduleName);
$moduleControlFile = $modulePath . 'control.php';
$actionExtPath = $this->app->getModuleExtPath($appName, $moduleName, 'control');
$file2Included = $moduleControlFile;
if(!empty($actionExtPath))
{
$commonActionExtFile = $actionExtPath['common'] . strtolower($methodName) . '.php';
$file2Included = file_exists($commonActionExtFile) ? $commonActionExtFile : $moduleControlFile;
if(!empty($actionExtPath['site']))
{
$siteActionExtFile = $actionExtPath['site'] . strtolower($methodName) . '.php';
$file2Included = file_exists($siteActionExtFile) ? $siteActionExtFile : $file2Included;
}
}
/**
* 加載控制器文件。
* Load the control file.
*/
if(!is_file($file2Included)) $this->app->triggerError("The control file $file2Included not found", __FILE__, __LINE__, $exit = true);
chdir(dirname($file2Included));
if($moduleName != $this->moduleName) helper::import($file2Included);
/**
* 設置調用的類名。
* Set the name of the class to be called.
*/
$className = class_exists("my$moduleName") ? "my$moduleName" : $moduleName;
if(!class_exists($className)) $this->app->triggerError(" The class $className not found", __FILE__, __LINE__, $exit = true);
/**
* 解析參數,創建模塊control對象。
* Parse the params, create the $module control object.
*/
if(!is_array($params)) parse_str($params, $params);
$module = new $className($moduleName, $methodName, $appName);
/**
* 調用對應方法,使用ob方法獲取輸出內容。
* Call the method and use ob function to get the output.
*/
ob_start();
call_user_func_array(array($module, $methodName), $params);
$output = ob_get_contents();
ob_end_clean();
/**
* 返回內容。
* Return the content.
*/
unset($module);
chdir($currentPWD);
return $output;
}
}
call_user_func_array(array($module, $methodName), $params);這個函數的調用相當于$module::$methodName($params),$methodName只能是public類型才可以,可以利用call_user_func_array調用php的任意內置類的public函數,也可以調用include的任意類,所以我在不斷嘗試之后,最終選擇調用baseDAO類的query函數去操縱數據庫,添加一個管理員賬號,因為然之后臺可以查看網站的絕對地址:

數據庫密碼:

執行任意命令:

關于poc的構造:
【1】首先是exp函數,因為數據傳輸是依靠aes加密傳輸的,而初始化的aes密鑰是88888888888888888888888888888888,所以把exp的json數據加密post給服務器端就好
而exp函數中的這個data就是整個exp的關鍵部分
data = '{"userID": "123","module": "chat","method": "fetch","params": {"0":"baseDAO","1":"query","2":"'+sql+'","3":"sys"}}'
【2】module是調用的模塊名字,因為受到限制,所以只能調用chat模塊,而method是調用的方法名字,因為這個沒有限制,所以就可以調用父類的函數fetch,傳遞進去的params又可以繼續調用其它模塊的其他函數,但是只能調用php中內置類的public函數,和include中的public函數,所以公共模塊是一個很好的利用點,而公共模塊中的數據庫操作函數最好下手,所以就調用baseDAO中query這個函數,往里面傳遞sql語句就可以控制數據庫了
【3】在數據庫中插入一個管理員的賬號之后就可以登陸后臺為所欲為了
然之登陸有點意思登陸函數是Login_ranzhi,ranzhi登陸前要先發get請求給登陸頁面,讓cookie獲取rid和頁面中獲取v.random。在登陸時要向登陸頁面發送賬號:account,密碼: password,密碼是由MD5(MD5(MD5(明文密碼)+賬號)+v.random)生成,原始密碼:rawPassword,由MD5(明文密碼)生成,keepLogin:false
【4】登陸后就可以在后臺獲得網站絕對路徑和數據庫帳戶名和密碼,也可以利用后臺執行任意命令
【5】因為然之演示站限制了很多函數的執行,所以利用exp添加管理員是可以做到的,后面執行系統命令被限制就無法去實現
漏洞披露
- 2018-01-08 給cnnvd提交漏洞
- 2018-01-09 給360補天提交漏洞
- 2018-01-11 cnnvd回復郵件確定漏洞真實存在
- 2018-01-12 360補天定為通用型漏洞
- 2018-2-24 提醒廠商修復漏洞,但是廠商開發人員認為影響不大,直到現在廠商未修復此漏洞
臨時防護建議:
在然之后臺進入-》后臺管理-》喧喧,把密鑰改成任意值
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/534/
暫無評論