Xiuno BBS 审计之问题:日志机制缺陷,无分级切割
贰先生 7小时前

 

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

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

现象

Xiuno BBS 4.0.4 仅有 xn_log() 一个日志函数,全站仅 6 个 PHP 文件调用(统计 18 处),日志按月分目录存放于 log/YYYYMM/,每类日志一个 .php 文件(前缀 <?php exit;?> 防止直接访问)。日志无级别字段(INFO/WARN/ERROR 混写)、无结构化字段(仅时间+IP+URL+UID+消息)、无日志切割(单文件无限增长)、无日志轮转/归档/清理策略、无远程日志收集。关键业务操作(登录、发帖、删除、支付)未强制记录日志,安全审计困难。

源码证据

1. xn_log 函数实现简陋,无级别、无结构化

文件:xiunobbs_4.0.4/xiunophp/misc.func.php 第 582-598 行

function xn_log($s, $file = 'error') {
    if(DEBUG == 0 && strpos($file, 'error') === FALSE) return;     // 线上仅记录含 error 的日志
    $time = $_SERVER['time'];
    $ip = $_SERVER['ip'];
    $conf = _SERVER('conf');
    $uid = intval(G('uid'));
    $day = date('Ym', $time);                                       // 按月分目录
    $mtime = date('Y-m-d H:i:s');
    $url = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
    $logpath = $conf['log_path'].$day;
    !is_dir($logpath) AND mkdir($logpath, 0777, true);

    $s = str_replace(array("\r\n", "\n", "\t"), ' ', $s);
    $s = "<?php exit;?>\t$mtime\t$ip\t$url\t$uid\t$s\r\n";          // 制表符分隔,无级别字段

    @error_log($s, 3, $logpath."/$file.php");                      // 单文件追加,无切割
}

2. xn_log 调用统计(Grep count)

xn_log  →  18 处 / 6 个文件

分布:

  • xiunophp/misc.func.php 5 处(含 error_handle 内部)
  • xiunophp/db.func.php 2 处(db_error)
  • xiunophp/xiunophp.min.php 7 处(合并文件,重复)
  • admin/route/index.php 2 处
  • route/user.php 1 处(仅 send_code 邮件失败)
  • model/attach.func.php 1 处

业务核心(route/post、route/thread、route/forum、route/mod、model/user、model/thread、model/post)零日志调用

3. 关键流程无日志:登录成功/失败、发帖、删帖、用户注册均不记日志

文件:xiunobbs_4.0.4/route/user.php 第 84-102 行(登录)

md5($password.$_user['salt']) != $_user['password'] AND message('password', lang('password_incorrect'));
// 登录失败无 xn_log
user_update($_user['uid'], array('login_ip'=>$longip, 'login_date' =>$time , 'logins+'=>1));
$uid = $_user['uid'];
$_SESSION['uid'] = $uid;
user_token_set($_user['uid']);
// hook user_login_post_end.php
message(0, lang('user_login_successfully'));                       // 登录成功无 xn_log

文件:xiunobbs_4.0.4/route/post.php 第 209-218 行(删帖)

if($isfirst) {
    thread_delete($tid);                                            // 删主题无日志
} else {
    post_delete($pid);                                              // 删回帖无日志
}
// hook post_delete_end.php
message(0, lang('delete_successfully'));

4. modlog.func.php 存在但仅记录版主操作,且未在删帖流程强制调用

文件:xiunobbs_4.0.4/model/modlog.func.php(Grep 显示 1 处 global、28 处 hook,但 route/post.php 删帖流程未调用 modlog_create)。

5. 日志文件无切割,单文件无限增长

xn_log 使用 error_log($s, 3, $file) 追加模式,无文件大小检查、无按天切割、无日志保留期清理。线上长时间运行后单文件可达 GB 级,tail/grep 难以操作。

6. 日志目录权限 0777,存在安全隐患

!is_dir($logpath) AND mkdir($logpath, 0777, true);

log/ 目录权限 0777,任意用户可读写,多租户共享主机下可被其他用户篡改/读取日志中的 IP、URL、UID 信息。

风险等级与结论

架构缺陷(中)

危害:

  • 业务核心(登录/发帖/删帖/注册)零日志,安全审计与攻击溯源无据可查
  • 日志无级别字段,INFO/WARN/ERROR 混写,无法按级别过滤
  • 无结构化(非 JSON),日志分析需正则解析,无法接入 ELK/Loki
  • 单文件无限增长,无切割轮转,运维成本高
  • DEBUG=0 时仅记录含 'error' 文件名的日志,业务日志被静默丢弃
  • 目录权限 0777,日志敏感信息(IP、URL、UID)可被同机其他用户读取

修复建议:

  • 引入 PSR-3 Logger 接口,支持 INFO/WARN/ERROR/DEBUG 分级
  • 日志格式改为 JSON Lines,字段包含 time,level,channel,uid,ip,url,trace_id,message,context
  • 关键业务操作(登录/发帖/删帖/支付/权限变更)强制记录审计日志
  • 接入 Monolog,支持文件切割(按天/按大小)+ 远程 Syslog/ELK
  • 日志目录权限改为 0750,敏感字段(IP)脱敏

现象

Xiuno BBS 4.0.4 通过 // hook xxx  <!--{hook xxx}--> 标记提供插件扩展点。经统计全站共约 697 处钩子点分布在 63 个文件中,看似覆盖面广,但关键业务流程(登录前置校验、发帖审核、支付/订单、附件上传、数据删除)的钩子点严重不足,且部分关键节点(如登录成功后写 session 前、密码校验失败、附件入库前、用户被删除前)完全缺失钩子,导致插件无法实现登录二次验证、发帖内容审核、支付集成、附件病 毒扫描等常见需求。

源码证据

1. 钩子点总量统计(Grep count)

// hook  → 697 处 / 63 个文件
<!--{hook  → 0 处(模板钩子用 // hook 同一正则匹配,源码注释 <!--{hook 在编译期被替换为 // hook,见 plugin.func.php 第 297 行)

2. 关键流程:登录缺关键钩子(user_login_post_end 在 message 之前但无登录失败钩子)

文件:xiunobbs_4.0.4/route/user.php 第 68-104 行

} else if($method == 'POST') {
    // hook user_login_post_start.php
    $email = param('email');
    $password = param('password');
    empty($email) AND message('email', lang('email_is_empty'));
    if(is_email($email, $err)) {
        $_user = user_read_by_email($email);
        empty($_user) AND message('email', lang('email_not_exists'));
    }
    !is_password($password, $err) AND message('password', $err);
    md5($password.$_user['salt']) != $_user['password'] AND message('password', lang('password_incorrect'));

    // 更新登录时间和次数
    user_update($_user['uid'], array('login_ip'=>$longip, 'login_date' =>$time , 'logins+'=>1));

    $uid = $_user['uid'];
    $_SESSION['uid'] = $uid;
    user_token_set($_user['uid']);
    // hook user_login_post_end.php        ← 仅有一个 end 钩子
    message(0, lang('user_login_successfully'));
}

缺失:无 user_login_pre_check(登录前可由插件接管二次验证/图形验证码/限制 IP)、无 user_login_failed(登录失败可记录风控/锁定账号)、无 user_login_success_before_session(写 session 前可改写登录态)。

3. 关键流程:发帖创建无审核前置钩子,插件无法拦截违规内容

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

} else {
    // hook post_post_start.php
    $message = param('message', '', FALSE);
    empty($message) AND message('message', lang('please_input_message'));

    $doctype = param('doctype', 0);
    xn_strlen($message) > 2028000 AND message('message', lang('message_too_long'));

    $thread['top'] > 0 AND thread_top_cache_delete();

    $quotepid = param('quotepid', 0);
    $quotepost = post__read($quotepid);
    (!$quotepost || $quotepost['tid'] != $tid) AND $quotepid = 0;

    $post = array(
        'tid'=>$tid,
        'uid'=>$uid,
        'create_date'=>$time,
        'userip'=>$longip,
        'isfirst'=>0,
        'doctype'=>$doctype,
        'quotepid'=>$quotepid,
        'message'=>$message,
    );
    $pid = post_create($post, $fid, $gid);     // 直接入库,无审核前置钩子
    empty($pid) AND message(-1, lang('create_post_failed'));
    // hook post_post_end.php

post_post_start 之后直接 post_create 入库,无 post_create_before 钩子供插件做内容审核/敏感词过滤/机器审核。

4. 关键流程:用户删除无前置钩子,无法级联清理第三方数据

文件:xiunobbs_4.0.4/route/post.php 第 181-220 行(仅 delete 后置钩子)

} elseif($action == 'delete') {
    $pid = param(2, 0);
    // hook post_delete_start.php
    if($method != 'POST') message(-1, lang('method_error'));
    $post = post_read($pid);
    // ...
    // hook post_delete_middle.php
    if($isfirst) {
        thread_delete($tid);           // 直接删除
    } else {
        post_delete($pid);
    }
    // hook post_delete_end.php
    message(0, lang('delete_successfully'));
}

5. 完全缺失的钩子点(无支付/订单流程)

Grep 全站无 payorderpayment 关键字钩子,无任何支付相关扩展点,无法通过插件接入支付宝/微信支付。

风险等级与结论

架构缺陷(严重)

危害:

  • 关键安全流程(登录、发帖、删除)无前置钩子,插件无法接管风控/审核/级联清理
  • 无支付/订单钩子,无法扩展为付费社区、积分商城等业务
  • 钩子分布不均(统计显示 697 个钩子集中在 model/*.func.php 内部,关键路由流程钩子稀疏)
  • 钩子命名不统一(xxx_startxxx_endxxx_beforexxx_afterxxx_middle 混用),插件作者难以预测

修复建议:

  • 为所有写操作(create/update/delete)统一增加 _before  _after 钩子对
  • 登录流程增加 user_login_pre_checkuser_login_faileduser_login_success_before_session
  • 引入支付/订单抽象层与对应钩子点
  • 制定钩子命名规范文档,并在 auto_add_hook.php 工具中校验
最新回复 (0)
全部楼主
返回