Xiuno BBS 审计之问题:插件出错无隔离,整站白屏
贰先生 4小时前

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

现象

Xiuno BBS 4.0.4 的插件/主题代码与核心代码共享同一 PHP 进程、同一全局变量空间、同一错误处理链路。一旦任意插件的 hook/overwrite/install.php 抛出致命错误(fatal error)、语法错误(parse error)或未捕获异常,整个站点立即白屏,包括管理后台。管理员无法通过 UI 禁用问题插件,只能 FTP 进服务器手动删除/重命名 plugin 目录或清空 tmp/ 缓存。

源码证据

文件:xiunobbs_4.0.4/model/plugin.func.php 第 15-37 行(编译产物直接 include)

function _include($srcfile) {
    global $conf;
    $len = strlen(APP_PATH);
    $tmpfile = $conf['tmp_path'].substr(str_replace('/', '_', $srcfile), $len);
    if(!is_file($tmpfile) || DEBUG > 1) {
        // 开始编译
        $s = plugin_compile_srcfile($srcfile);
        // ...
        file_put_contents_try($tmpfile, $s);
        $s = plugin_compile_srcfile($tmpfile);
        file_put_contents_try($tmpfile, $s);
    }
    return $tmpfile;
}

编译产物 $tmpfile 是 PHP 文件,被调用方 include _include(...) 直接执行。若插件 hook/overwrite 文件存在语法错误,编译写入的 tmp 文件即包含错误,include 时整个进程 fatal error。

文件:xiunobbs_4.0.4/index.php 第 50-52 行(核心入口直接 include 编译产物)

include APP_PATH.'model/plugin.func.php';
include _include(APP_PATH.'model.inc.php');
include _include(APP_PATH.'index.inc.php');

文件:xiunobbs_4.0.4/model.inc.php 第 36-62 行(model 文件合并为 model.min.php,单点错误污染全部)

if(DEBUG) {
    foreach ($include_model_files as $model_files) {
        include _include($model_files);
    }
} else {
    $model_min_file = $conf['tmp_path'].'model.min.php';
    $isfile = is_file($model_min_file);
    if(!$isfile) {
        $s = '';
        foreach($include_model_files as $model_files) {
            $t = file_get_contents(_include($model_files));
            $t = trim($t);
            $t = ltrim($t, '<?php');
            $t = rtrim($t, '?>');
            $s .= "<?php\r\n".$t."\r\n?>";
        }
        $r = file_put_contents($model_min_file, $s);     // 全部 model 拼接为一个文件
        unset($s);
    }
    include $model_min_file;                              // 任一 model 出错,全站白屏
}

文件:xiunobbs_4.0.4/model/plugin.func.php 第 342-389 行(hook 内容直接字符串拼接进编译产物)

function plugin_compile_srcfile_callback($m) {
    // ...
    foreach($hooks[$hookname] as $path) {
        $t = file_get_contents($path);
        if($fileext == 'php' && preg_match('#^\s*<\?php\s+exit;#is', $t)) {
            $t = preg_replace('#^\s*<\?php\s*exit;(.*?)(?:\?>)?\s*$#is', '\\1', $t);
        }
        $s .= $t;   // 插件 hook 内容直接拼接到编译产物,无 try-catch、无语法校验
    }
    return $s;
}

风险等级与结论

风险等级:严重(Critical)|生态缺陷

危害:

  1. 单个插件语法错误 / fatal error 导致全站白屏,包括管理后台,管理员无法通过 UI 恢复。
  2. model.min.php 把所有 model + 插件 hook 拼接为一个文件,任一插件 hook 出错,所有 model 函数全部失效。
  3. _include() 在 DEBUG>1 时会重新编译,开发环境下偶发的插件错误直接拖垮整站。
  4. tmp/ 缓存被污染后,即便禁用插件($conf['disabled_plugin'])也需要手动清缓存才能恢复。
  5. 没有插件健康度检测机制,无法自动禁用持续报错的插件。

修复建议:

  •  plugin_compile_srcfile_callback 拼接 hook 后,使用 php -l 校验语法,失败则跳过该 hook 并记录日志。
  • _include() 编译失败时回退到原文件,并设置全局警告。
  • 引入"安全模式"路由 admin/plugin/safe_mode,通过 URL 参数 ?disable_all_plugins=1 临时禁用所有插件,方便管理员恢复。
  • model 文件放弃合并为 model.min.php,改为按需加载,缩小错误爆炸半径。
  • 提供 plugin_health_check() 函数,每次请求捕获 fatal error 并自动禁用肇祸插件(记录到 conf/plugin_disabled_auto.json)。
  • tmp/ 目录增加 .htaccess / nginx 规则禁止直接访问,避免缓存文件被恶意构造访问。
最新回复 (0)
全部楼主
返回