文章目錄

##测试方式
漏洞来源: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
1

2,修改一个资料字段为要删除的目标地址。这里使用birthprovince字段。记录下formhash,备用。
1

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 就可以了。

1

##代码分析

分析版本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表。查询数据库中该表字段内容如下:

1

经过灰盒测试发现,$_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表中记录:

1

没有任何一个字段的type是file。所以这里这语句应该是永远都到达不了的。也就没有办法绕过了。所以修复的话可以考虑加这个判断,甚至直接注释掉相关代码代码。如果分析有错误,欢迎指正。

文章目錄