XIUNO BBS X版 重构记录贴(一)安全加固与 PHP 8 兼容

安全加固与 PHP 8 兼容

Why

Xiuno BBS 4.0 核心代码使用 mysql_* 等已在 PHP 7.0 移除的函数,无法在 PHP 8.0+ 运行;同时存在密码明文 MD5+salt 存储、无 CSRF 防护、无登录失败限制等高危安全漏洞。本阶段目标是让核心代码在 PHP 8.0+ 无错运行并修复所有已知高危安全问题。

What Changes

  • 修复 PHP 8 不兼容语法(&neweach()create_function()preg_replace /e 等)

  • BREAKING 移除 db_mysql.class.php,将 mysql 驱动默认切换为 pdo_mysql;保留 db_* 全局函数签名不变

  • BREAKING user 表新增 password_hash 字段,登录验证逻辑改为优先 password_verify

  • user 表新增 login_attemptslast_login_iplast_login_timebanned_until 字段

  • 新增 user_login_log

  • 所有 POST 表单新增 csrf_token 隐藏字段,服务端统一验证

  • 输出转义:默认 htmlspecialchars($var, ENT_QUOTES | ENT_HTML5),富文本经 HTMLPurifier

  • 新增 CsrfServiceLoginSecurityService 服务类

  • 新增 install/upgrade_phase1.sql 升级脚本

Impact

  • Affected specs: 数据库层、用户认证、表单处理、输出渲染

  • Affected code:

  • xiunophp/db_mysql.class.php — 标记废弃,不再默认加载

  • xiunophp/db.func.phpdb_new() 移除 mysql case

  • xiunophp/db_pdo_mysql.class.php — 成为默认驱动

  • xiunophp/xiunophp.min.php — 同步移除 db_mysql 代码

  • model/user.func.php — 新增 user_login_verify()user_login_attempt()user_login_log()

  • route/user.php — 登录逻辑重写,POST 处理前验证 CSRF

  • route/*.php — 所有 POST 分支前加 CSRF 验证

  • view/htm/*.htm — 所有 <form> 内加 csrf_token 隐藏字段

  • index.inc.php — 生成 CSRF token 并存入 session

  • install/install.sql — 新增字段和表

  • conf/conf.default.php — 新增安全配置项

  • xiunophp/xn_html_safe.func.php — 增强为 HTMLPurifier 封装

ADDED Requirements

Requirement: PHP 8 语法兼容

系统 SHALL 在 PHP 8.0 ~ 8.3 下无错运行,不使用任何已移除函数或语法。

Scenario: 移除 mysql_* 函数
  • WHEN 配置 db.type = 'mysql'

  • THEN 系统输出明确错误提示 "mysql driver removed, please use pdo_mysql",并拒绝启动

Scenario: 移除 &new 语法
  • WHEN 代码中出现 $a = &new C() 模式

  • THEN 替换为 $a = new C()

Scenario: each() 替换
  • WHEN 代码中使用 each() 函数

  • THEN 替换为 foreachkey()/current()/next() 组合

Requirement: PDO 数据库驱动迁移

系统 SHALL 将 db_mysql.class.php 标记为 @deprecated,默认使用 db_pdo_mysql.class.php,保留所有 db_* 全局函数签名不变。

Scenario: 默认驱动切换
  • WHEN conf.phpdb.typemysql

  • THEN 自动映射为 pdo_mysql 并记录警告日志

Scenario: db_* 函数兼容
  • WHEN 插件调用 db_find()db_exec() 等函数

  • THEN 行为与旧版完全一致,签名不变

Requirement: 密码哈希迁移

系统 SHALL 支持从 MD5+salt 到 password_hash() 的渐进式迁移。

Scenario: 新用户注册
  • WHEN 用户注册时

  • THEN 使用 password_hash($password, PASSWORD_DEFAULT) 存储,password_hash 字段写入值,passwordsalt 字段保留但不再使用

Scenario: 旧用户登录自动升级
  • WHEN 用户登录且 password_hash 字段为空

  • THEN 先用旧方式 md5($password.$salt) 验证;验证成功后自动用 password_hash() 生成新哈希写入 password_hash 字段

Scenario: 新密码验证
  • WHEN 用户登录且 password_hash 字段非空

  • THEN 使用 password_verify($password, $password_hash) 验证

Scenario: 批量迁移脚本
  • WHEN 管理员运行 cli/migrate_passwords.php

  • THEN 所有 password_hash 为空且 password 非空的用户被标记为待迁移(不批量解密,仅标记),下次登录时自动升级

Requirement: CSRF Token 验证

系统 SHALL 为所有 POST/PUT/DELETE 请求验证 CSRF Token。

Scenario: Token 生成
  • WHEN 用户访问任何页面

  • THEN 系统在 session 中生成随机 csrf_token,并通过 $header['csrf_token'] 传递给模板

Scenario: 表单提交
  • WHEN 用户提交 POST 表单

  • THEN 服务端验证 $_POST['csrf_token']$_SESSION['csrf_token'] 一致;不一致则返回错误

Scenario: AJAX 请求
  • WHEN 前端通过 AJAX 发送 POST 请求

  • THEN 请求头 X-CSRF-Token 或参数 csrf_token 必须携带有效 token

Scenario: Token 轮换
  • WHEN CSRF 验证成功后

  • THEN 不立即轮换 token(保持会话级 token),避免多标签页问题

Requirement: 输出转义

系统 SHALL 对所有输出进行安全转义。

Scenario: 纯文本输出
  • WHEN 模板输出用户提交的纯文本

  • THEN 使用 htmlspecialchars($var, ENT_QUOTES | ENT_HTML5, 'UTF-8') 转义

Scenario: 富文本输出
  • WHEN 模板输出帖子内容等富文本

  • THEN 经过 HTMLPurifier 过滤,仅允许白名单标签和属性

Requirement: 登录失败限制

系统 SHALL 限制登录失败次数,防止暴力破解。

Scenario: 失败计数
  • WHEN 用户登录失败

  • THEN login_attempts 字段 +1,记录 last_login_iplast_login_time

Scenario: 账户锁定
  • WHEN login_attempts >= 配置的最大次数(默认 5)

  • THEN 账户锁定至 banned_until 时间(默认 15 分钟),期间拒绝登录

Scenario: 登录成功
  • WHEN 用户登录成功

  • THEN login_attempts 重置为 0,banned_until 清空,写入 user_login_log

Scenario: 登录日志
  • WHEN 每次登录(成功或失败)

  • THEN 写入 user_login_log 表(uid, ip, time, success, user_agent)

Requirement: 升级脚本

系统 SHALL 提供 SQL 升级脚本,支持从 4.0 无损升级。

Scenario: 执行升级
  • WHEN 管理员执行 install/upgrade_phase1.sql

  • THEN user 表新增 password_hashlogin_attemptslast_login_iplast_login_timebanned_until 字段;创建 user_login_log 表;所有新字段有合理默认值,不影响现有数据

MODIFIED Requirements

Requirement: db_new() 函数

原实现支持 mysql 类型。修改后:mysql case 移除,映射为 pdo_mysql 并记录日志。函数签名 db_new($dbconf) 不变。

Requirement: user_login 验证逻辑

原实现使用 md5($password.$user['salt']) == $user['password']。修改后:优先 password_verify(),回退旧方式并自动升级。登录前检查 banned_until

Requirement: POST 路由处理

原实现直接处理 POST 参数。修改后:所有 POST 分支在业务逻辑前调用 csrf_check()

REMOVED Requirements

Requirement: db_mysql 驱动

Reason: mysql_* 函数在 PHP 7.0 已移除,无法在 PHP 8.0+ 运行

Migration: conf.phpdb.typemysql 改为 pdo_mysqldb_mysql.class.php 文件保留但标记 @deprecated,不再被 db_new() 加载


作者:贰先生
链接:https://juejin.cn/post/7647463706607009835
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

最新回复

请先登录后再回复 登录

uid:1 管理
关注
随遇而安,随缘而行
发帖 39
评论 220
粉丝 9
关注 1
发新帖
目录

扫码手机打开本帖