discuz X 任意文件删除漏洞学习
##测试方式
漏洞来源:http://www.wooyun.org/bugs/wooyun-2010-065513
先说一下这个漏洞的利用过程吧。这个原帖里已经写的比较清楚了。注册用户。
1,访问:http://localhost/Discuz_X3.1_SC_UTF8/upload/home.php?mod=spacecp&ac=profile&op=base
2,修改一个资料字段为要删除的目标地址。这里使用birthprovince字段。记录下formhash,备用。
3,post profilesubmit=1&formhash=c9121ba1(自己的formhash)到http://localhost/Discuz_X3.1_SC_GBK/upload/home.php?mod=spacecp&ac=profile&op=base&deletefile[birthprovince]=aaa 就可以了。
##代码分析
分析版本discuzX3.1最新版 缺陷代码:/upload/source/include/spacecp/spacecp_profile.php
if($_GET['deletefile'] && is_array($_GET['deletefile'])) {
foreach($_GET['deletefile'] as $key => $value) {
if(isset($_G['cache']['profilesetting'][$key])) {
@unlink(getglobal('setting/attachdir').'./profile/'.$space[$key]);
@unlink(getglobal('setting/attachdir').'./profile/'.$verifyinfo['field'][$key]);
$verifyarr[$key] = $setarr[$key] = '';
}
}
}
$space[$key]和$verifyinfo[‘field’][$key]的来源。变量进入了unlink函数。寻找$space[$key]和$verifyinfo[‘field’][$key]的来源。
定位到23行:
$space = getuserbyuid函数。($_G['uid']);
space_merge($space, 'field_home');
space_merge($space, 'profile');
$_G[‘uid’]是用户的ID,跟踪getuserbyuid函数。/upload/source/function/function_core.php 76行:
function getuserbyuid($uid, $fetch_archive = 0) {
static $users = array();
if(empty($users[$uid])) {
$users[$uid] = C::t('common_member'.($fetch_archive === 2 ? '_archive' : ''))->fetch($uid);
if($fetch_archive === 1 && empty($users[$uid])) {
$users[$uid] = C::t('common_member_archive')->fetch($uid);
}
}
if(!isset($users[$uid]['self']) && $uid == getglobal('uid') && getglobal('uid')) {
$users[$uid]['self'] = 1;
}
return $users[$uid];
}
getuserbyuid查询的pre_common_member表的数据。
space_merge($space, 'field_home');
space_merge($space, 'profile');
是把pre_common_member_field_home和pre_common_member_profile表合并到$space中。所以$space一共92个成员,最后。
$space = getuserbyuid函数。($_G['uid']); (24个字段)
space_merge($space, 'field_home'); (18个字段)
space_merge($space, 'profile'); (52个字段) 都有uid字段合并。
分析完$space就发现,这三个表有太多可以控制的变量了。接下来分析删除文件的流程,有个判断条件:
if(isset($_G['cache']['profilesetting'][$key])) 这个条件为真才行。
在文件/upload/source/function/function_profile.php 中
if(empty($_G['cache']['profilesetting'])) {
loadcache('profilesetting');
}
跟踪loadcache函数:
function loadcache($cachenames, $force = false) {
global $_G;
static $loadedcache = array();
$cachenames = is_array($cachenames) ? $cachenames : array($cachenames);
$caches = array();
foreach ($cachenames as $k) {
if(!isset($loadedcache[$k]) || $force) {
$caches[] = $k;
$loadedcache[$k] = true;
}
}
if(!empty($caches)) {
$cachedata = C::t('common_syscache')->fetch_all($caches);
foreach($cachedata as $cname => $data) {
if($cname == 'setting') {
$_G['setting'] = $data;
} elseif($cname == 'usergroup_'.$_G['groupid']) {
$_G['cache'][$cname] = $_G['group'] = $data;
} elseif($cname == 'style_default') {
$_G['cache'][$cname] = $_G['style'] = $data;
} elseif($cname == 'grouplevels') {
$_G['grouplevels'] = $data;
} else {
$_G['cache'][$cname] = $data;
}
}
}
return true;
}
发现$_G[‘cache’][‘profilesetting’]来自于common_syscache表。查询数据库中该表字段内容如下:
经过灰盒测试发现,$_G[‘cache’][‘profilesetting’]一共有37个成员。是一部分pre_common_member_profile表的字段。所以,这里利用的话只能利用common_member_profile表中的可控字段。
##修复方案
截止到目前X3.1没有发布补丁,X3.2在原安装包上进行了修复,也没有发布单独的补丁。所以在2014-06-20甚至更后一段时间之前安装的discuz程序都存在这个漏洞。而由于官方没有发布补丁,所以相当一部分包括大型互联网公司的论坛都没有修复这个漏洞。只是这个漏洞的利用方式删除文件,获取shell的方式可以通过删除安装锁定文件重装拿shell。只是这样子的方式过于粗暴,如果有更优雅的二次利用可以获取shell的话,这个漏洞肯定会引起更多的关注。
X3.2的修复之后为:
if($_GET['deletefile'] && is_array($_GET['deletefile'])) {
foreach($_GET['deletefile'] as $key => $value) {
//var_dump($_G['cache']['profilesetting']);
if(isset($_G['cache']['profilesetting'][$key]) && $_G['cache']['profilesetting'][$key]['formtype'] == 'file') {
@unlink(getglobal('setting/attachdir').'./profile/'.$space[$key]);
//echo '11111111';
@unlink(getglobal('setting/attachdir').'./profile/'.$verifyinfo['field'][$key]);
$verifyarr[$key] = $setarr[$key] = '';
加了一个判断 $_G[‘cache’][‘profilesetting’][$key][‘formtype’] == ‘file’ 为true。前面分析的时候提到过,$_G[‘cache’][‘profilesetting’]来自于pre_common_member_profile表,而每个字段的formtype在pre_common_member_profile_setting表中记录:
没有任何一个字段的type是file。所以这里这语句应该是永远都到达不了的。也就没有办法绕过了。所以修复的话可以考虑加这个判断,甚至直接注释掉相关代码代码。如果分析有错误,欢迎指正。