此文章为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;
$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;
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;
$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;
$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 对象,杜绝全局状态污染