Xiuno BBS 审计之问题:全局变量滥用,状态混乱
贰先生 5小时前

此文章为XIUNOX版本重构审计时发现问题,XIUNOX版本已优化修复此问题。分享出来方便后续想基于xiuno bbs4.0.4版本制作维护版本或插件模板等需求的开发者和站长参考。

现象

Xiuno BBS 4.0.4 大量使用 global $var 在函数间共享状态,全站共 131 处 global $ 声明分布在 28 个 PHP 文件中。配置 $conf、数据库 $db、缓存 $cache、运行时 $time、用户 $uid、错误 $errno/$errstr、会话 $g_session、插件 $plugins 等核心状态均通过 global 共享,部分关键变量还被同时写入 $_SERVER 超全局变量作"备份"。这种模式导致函数副作用不可预测、单元测试无法隔离、并发请求在单进程内可能串状态、重命名/重构成本极高。

源码证据

1. 全局变量声明统计(Grep count 模式)

global $  →  131 处 / 28 个文件

重灾区文件(部分):

  • model/plugin.func.php 19 处
  • model/user.func.php 17 处
  • model/post.func.php 7 处 / model/thread.func.php 8 处
  • model/session.func.php 7 处
  • xiunophp/xiunophp.min.php 6 处
  • model/runtime.func.php 6 处

2. plugin.func.php 中 19 处 global 声明,函数副作用不可预测

文件:xiunobbs_4.0.4/model/plugin.func.php 第 54-101 行

function plugin_init() {
    global $plugin_srcfiles, $plugin_paths, $plugins, $official_plugins;     // 4 个 global
    // ...
    $plugin_paths = glob(APP_PATH.'plugin/*', GLOB_ONLYDIR);
    if(is_array($plugin_paths)) {
        foreach($plugin_paths as $path) {
            $dir = file_name($path);
            $conffile = $path."/conf.json";
            if(!is_file($conffile)) continue;
            $arr = xn_json_decode(file_get_contents($conffile));
            if(empty($arr)) continue;
            $plugins[$dir] = $arr;
            $plugins[$dir]['hooks'] = array();
            // ...
        }
    }
}

文件:xiunobbs_4.0.4/model/plugin.func.php 第 145-188 行

function plugin_enable($dir) {
    global $plugins;                                // 改全局状态
    if(!isset($plugins[$dir])) return FALSE;
    $plugins[$dir]['enable'] = 1;
    file_replace_var(APP_PATH."plugin/$dir/conf.json", array('enable'=>1), TRUE);
    plugin_clear_tmp_dir();
    return TRUE;
}
function plugin_disable($dir) {
    global $plugins;
    if(!isset($plugins[$dir])) return FALSE;
    $plugins[$dir]['enable'] = 0;
    file_replace_var(APP_PATH."plugin/$dir/conf.json", array('enable'=>0), TRUE);
    plugin_clear_tmp_dir();
    return TRUE;
}

3. session.func.php 直接修改全局 $g_session,sess_write 又读 global

文件:xiunobbs_4.0.4/model/session.func.php 第 29-52 行

function sess_read($sid) {
    global $g_session, $longip, $time;                  // 3 个 global
    if(empty($sid)) {
        $sid = session_id();
        sess_new($sid);
        return '';
    }
    $arr = db_find_one('session', array('sid'=>$sid));
    // ...
    $g_session = $arr;                                  // 写全局
    return $arr ? $arr['data'] : '';
}

第 106-167 行:

function sess_write($sid, $data) {
    global $g_session, $time, $longip, $g_session_invalid, $conf;    // 5 个 global
    $uid = _SESSION('uid');
    $fid = _SESSION('fid');
    // ...
    $update = array_diff_value($arr, $g_session);       // 读全局
    db_update('session', array('sid'=>$sid), $update);
}

4. xiunophp.php 把核心对象同时塞 global 和 $_SERVER,双轨制

文件:xiunobbs_4.0.4/xiunophp/xiunophp.php 第 100-130 行

$_SERVER['starttime'] = $starttime;
$_SERVER['time'] = $time;
$_SERVER['ip'] = $ip;
$_SERVER['longip'] = $longip;
$_SERVER['useragent'] = $useragent;
$_SERVER['conf'] = $conf;
$_SERVER['lang'] = $lang;
$_SERVER['errno'] = $errno;
$_SERVER['errstr'] = $errstr;
$_SERVER['method'] = $method;
$_SERVER['ajax'] = $ajax;
// ...
$_SERVER['db'] = $db;
$_SERVER['cache'] = $cache;

第 1-3 行的 db.func.php 函数则反过来读 $_SERVER['db']

function db_sql_find_one($sql, $d = NULL) {
    $db = $_SERVER['db'];
    $d = $d ? $d : $db;
    // ...
}

 model/*.func.php 中又用 global $conf 读同一份数据,三轨并存。

5. user.func.php 中 17 处 global,包含 $g_static_users 进程内缓存

文件:xiunobbs_4.0.4/model/user.func.php 第 54-75 行

function user_update($uid, $arr) {
    global $conf, $g_static_users;                  // 2 个 global
    $r = user__update($uid, $arr);
    $conf['cache']['type'] != 'mysql' AND cache_delete("user-$uid");
    isset($g_static_users[$uid]) AND $g_static_users[$uid] = array_merge($g_static_users[$uid], $arr);
    return $r;
}
function user_read($uid) {
    global $g_static_users;
    if(empty($uid)) return array();
    $uid = intval($uid);
    $user = user__read($uid);
    user_format($user);
    $g_static_users[$uid] = $user;                  // 进程内缓存写全局
    return $user;
}

风险等级与结论

架构缺陷(严重)

危害:

  • 131 处 global 声明使函数行为依赖隐式外部状态,调用顺序敏感、不可单元测试
  • $conf$db$cache 同时通过 global 与 $_SERVER 双轨暴露,命名空间污染严重
  • $g_static_users 等进程内缓存与 global 绑定,长生命周期进程(如 php-fpm 复用 worker)可能跨请求串数据
  • 错误状态 $errno/$errstr 为全局变量,嵌套调用会被覆盖,错误追溯困难
  • 重构成本极高:改名任何一个全局变量需同步修改 N 个文件
  • 与"无依赖注入容器"缺陷叠加,无法 mock 替换依赖

修复建议:

  • 引入依赖注入容器(如 Container::get('db')),函数通过参数接收依赖而非 global
  •  $conf$db$cache 封装为 Config/DB/Cache 类的静态方法或单例
  • $g_static_users 等 request 级缓存改为 Request 上下文对象属性,请求结束自动释放
  • $errno/$errstr 改为异常或 Result 对象,杜绝全局状态污染
最新回复 (0)
全部楼主
返回