此文章为XIUNOX版本重构审计时发现问题,XIUNOX版本已优化修复此问题。分享出来方便后续想基于xiuno bbs4.0.4版本制作维护版本或插件模板等需求的开发者和站长参考。
现象
Xiuno BBS 4.0.4 没有依赖注入容器(DI Container),核心依赖对象($db、$cache、$conf、$lang)通过两种方式暴露给业务代码:一是 global $var 声明(与"全局变量泛滥"缺陷叠加),二是塞入 $_SERVER 超全局变量(如 $_SERVER['db']、$_SERVER['cache'])。函数内部用 _SERVER('db') 或 global $db 取依赖,无法 mock、无法替换、无法测试、无法支持多实例。$_SERVER 超全局变量本应仅用于存储 PHP 服务器环境信息,被滥用为容器。
源码证据
1. xiunophp.php 把核心依赖塞入 $_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['get_magic_quotes_gpc'] = $get_magic_quotes_gpc;
$db = !empty($conf['db']) ? db_new($conf['db']) : NULL;
$conf['cache']['mysql']['db'] = $db;
$cache = !empty($conf['cache']) ? cache_new($conf['cache']) : NULL;
unset($conf['cache']['mysql']['db']);
$_SERVER['db'] = $db;
$_SERVER['cache'] = $cache;
2. db.func.php 函数读 $_SERVER['db'] 取依赖
文件:xiunobbs_4.0.4/xiunophp/db.func.php 第 49-58 行
function db_sql_find_one($sql, $d = NULL) {
$db = $_SERVER['db'];
$d = $d ? $d : $db;
if(!$d) return FALSE;
$arr = $d->sql_find_one($sql);
db_errno_errstr($arr, $d, $sql);
return $arr;
}
文件:xiunobbs_4.0.4/xiunophp/db.func.php 第 28-37 行
function db_connect($d = NULL) {
$db = $_SERVER['db'];
$d = $d ? $d : $db;
$r = $d->connect();
db_errno_errstr($r, $d);
return $r;
}
3. cache.func.php 同样读 $_SERVER['cache']
文件:xiunobbs_4.0.4/xiunophp/cache.func.php 第 23-33 行
function cache_get($k, $c = NULL) {
$cache = $_SERVER['cache'];
$c = $c ? $c : $cache;
if(!$c) return FALSE;
strlen($k) > 32 AND $k = md5($k);
$k = $c->cachepre.$k;
$r = $c->get($k);
return $r;
}
4. model 层函数读 global $conf 取配置(双轨制)
文件:xiunobbs_4.0.4/model/user.func.php 第 54-63 行
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;
}
而 misc.func.php 的 url() 函数却从 $_SERVER 取配置:
文件:xiunobbs_4.0.4/model/misc.func.php 第 14-17 行
function url($url, $extra = array()) {
$conf = _SERVER('conf');
!isset($conf['url_rewrite_on']) AND $conf['url_rewrite_on'] = 0;
}
5. lang() 函数读 $_SERVER['lang']
文件:xiunobbs_4.0.4/xiunophp/misc.func.php 第 174-184 行
function lang($key, $arr = array()) {
$lang = $_SERVER['lang'];
if(!isset($lang[$key])) return 'lang['.$key.']';
$s = $lang[$key];
if(!empty($arr)) {
foreach($arr as $k=>$v) {
$s = str_replace('{'.$k.'}', $v, $s);
}
}
return $s;
}
6. 无 Container 类、无 register/resolve/get 等容器接口
Grep 全站无 Container、DI、register、resolve、make 等容器相关关键字(除 cache_new/db_new 工厂函数)。所有依赖获取均为隐式 global 或 $_SERVER。
7. 无 mock 能力,单元测试无法隔离依赖
业务函数内部直接读 $_SERVER['db'],测试时无法替换为 mock 对象。只能通过 $_SERVER['db'] = $mock 全局替换,污染其他测试用例。
风险等级与结论
架构缺陷(严重)
危害:
$_SERVER 超全局变量被滥用为容器,违反 PHP 语言设计意图($_SERVER 应仅存服务器环境信息)
- 同一依赖(如
$conf)通过 global 与 $_SERVER 双轨暴露,命名空间混乱
- 无 mock 能力,单元测试无法隔离数据库/缓存依赖,业务代码不可测试
- 多实例场景(如同时连接两个数据库)只能通过
$d 参数手动传递,无容器管理生命周期
- 依赖创建与使用耦合,
db_new/cache_new 在 xiunophp.php 启动时硬编码实例化,无法延迟加载
- 无依赖注入接口,第三方插件无法替换核心依赖(如自定义 cache 实现)
- 与"全局变量泛滥"缺陷叠加,函数副作用完全不可控
修复建议:
- 引入轻量 DI 容器(如
class Container { static function get($name) {...} })
- 核心依赖通过
Container::register('db', function() {...}) 注册,Container::get('db') 获取
- 业务函数通过参数接收依赖(依赖注入),而非内部
global/$_SERVER 读取
- 支持 mock:测试时
Container::set('db', $mockDb) 替换
- 支持延迟加载:
db 仅在首次 Container::get('db') 时实例化
- 废弃
$_SERVER['db']/$_SERVER['cache']/$_SERVER['conf'] 等用法,统一走容器