Xiuno BBS 审计之问题:无统一异常处理,错误外泄
贰先生 6小时前

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

现象

Xiuno BBS 4.0.4 业务代码(route/*.phpmodel/*.func.php)中几乎不存在 try/catch  throw,错误通过全局变量 $errno/$errstr + xn_error() 函数传递,调用方仅能通过 === FALSE 返回值感知错误,错误信息靠 global $errstr 读取。这种"全局错误变量"模式在嵌套调用时会被覆盖、无法定位错误源头。同时 trigger_error 散落各处(统计 15 处),部分致命错误直接 exit  message(-1, ...) 终止流程,无统一异常处理器收集上下文。

源码证据

1. 业务代码 try/catch/throw 统计(Grep count)

try {          → 业务代码(route/、model/)中 0 处,仅在 xiunophp 库层与 js 文件
throw new      → 业务代码 0 处(仅 xiunophp/xn_zip_old.func.php 3 处、xn_send_mail 21 处)
catch (        → 业务代码 0 处
trigger_error  → 15 处 / 9 个文件(散落在 xiunophp 库类)

2. xn_error 用 global 变量记录错误,嵌套调用会被覆盖

文件:xiunobbs_4.0.4/xiunophp/misc.func.php 第 56-61 行

function xn_error($no, $str, $return = FALSE) {
    global $errno, $errstr;
    $errno = $no;
    $errstr = $str;
    return $return;
}

调用方模式(db.func.php 第 17-22 行):

if(!$db || ($db && $db->errstr)) {
    $errno = -1;
    $errstr = $db->errstr;          // 直接赋值 global,无栈信息
    return FALSE;
}

3. db_errno_errstr 写 global 错误变量,调用链丢失

文件:xiunobbs_4.0.4/xiunophp/db.func.php 第 201-209 行

function db_errno_errstr($r, $d = NULL, $sql = '') {
    global $errno, $errstr;
    if($r === FALSE) {
        $errno = $d->errno;
        $errstr = db_errstr_safe($errno, $d->errstr);
        $s = 'SQL:'.$sql."\r\nerrno: ".$errno.", errstr: ".$errstr;
        xn_log($s, 'db_error');
    }
}

业务调用方仅能 if ($r === FALSE) message(-1, $errstr) 读取最新一次错误,嵌套调用上层错误已被覆盖。

4. error_handle 仅在 DEBUG>0 时生效,线上错误被静默

文件:xiunobbs_4.0.4/xiunophp/misc.func.php 第 21-53 行

function error_handle($errno, $errstr, $errfile, $errline) {
    if(DEBUG == 0)  return FALSE;                   // 线上直接返回,错误被吞
    // ...
    $subject = "Error[$errno]: $errstr, File: $errfile, Line: $errline";
    xn_log($subject, 'php_error');
    // ...
    echo ($ajax || IN_CMD) ? $txt : $html;
    DEBUG == 2 AND xn_log($txt, 'debug_error');
    return TRUE;
}

文件:xiunobbs_4.0.4/xiunophp/xiunophp.php 第 80 行

DEBUG AND set_error_handler('error_handle', -1);    // 仅 DEBUG 时注册

线上 DEBUG=0  set_error_handler 不注册,PHP 错误按默认行为(写 error_log 后吞掉),业务层无感知。

5. message() 函数直接 exit,无法被 try/catch 捕获

文件:xiunobbs_4.0.4/model/misc.func.php 第 92-114 行

function message($code, $message, $extra = array()) {
    // ...
    static $called = FALSE;
    $called ? exit(xn_json_encode($arr)) : $called = TRUE;
    if($ajax) {
        echo xn_json_encode($arr);
    } else {
        // ...
    }
    exit;                                            // 直接 exit
}

业务代码大量使用 empty($email) AND message('email', lang('email_is_empty')),等同于 if (...) { exit; },无法被外层 try/catch 接管做统一日志/告警/事务回滚。

6. 业务代码典型错误处理:返回 FALSE + global errstr

文件:xiunobbs_4.0.4/route/post.php 第 71 行

$pid = post_create($post, $fid, $gid);
empty($pid) AND message(-1, lang('create_post_failed'));    // 失败原因丢失

post_create 内部失败时 $errstr 已被覆盖,但调用方只 message(-1, lang('create_post_failed')) 显示固定文案,不读取 $errstr,错误信息彻底丢失。

风险等级与结论

架构缺陷(严重)

危害:

  • 业务代码 0 try/catch,任何运行时错误都直接终止流程,无法优雅降级
  • 全局 $errno/$errstr 在嵌套调用中被覆盖,错误源头不可追溯
  • message() 直接 exit,事务中调用会导致事务未回滚、连接未释放
  • 线上 DEBUG=0  error_handle 不注册,PHP 错误被静默吞掉,故障难定位
  • 错误信息丢失:业务调用方常用固定文案 message(-1, lang('xxx_failed')) 而非读取 $errstr
  • 无统一异常处理器,无法集中收集错误上下文(请求参数、用户、SQL、调用栈)

修复建议:

  • 引入业务异常类 BusinessExceptionSystemExceptionmessage() 改为抛异常
  • 全局注册 set_exception_handler  set_error_handler(无论 DEBUG 值),统一记录日志 + 格式化响应
  • 废弃 xn_error + global 模式,函数返回 Result 对象或抛异常
  • 事务边界包裹 try/catch,确保异常时回滚
最新回复 (0)
全部楼主
返回