[开发]第一轮修复完成测试记录

👑Lv.11 元老 🌞 骨干会员
2026-10-23 23:54:34

本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:7月5日复制的资料/修复完成测试.md。

第一轮修复完成测试记录

修复日期:2026-07-01

覆盖范围:🔴 致命漏洞 6 项(7 个文件)


1.1 ZIP 解压 RCE — Admin/ThemeController.php

改动文件: app/Controllers/Admin/ThemeController.php

修复内容

  • 解压前遍历 ZIP 内所有文件,对每个文件后缀做白名单校验
  • 白名单范围:css, js, png, jpg, jpeg, gif, svg, ico, json, html, htm, woff, woff2, ttf, eot
  • 目录已存在时改为先删除旧目录再覆盖(原为报错提示)
  • 解压后二次扫描目录,清理非白名单文件
  • 解压完成后刷新主题缓存

测试要点

  • [ ] 上传含 .php 文件的 ZIP → 应拒绝
  • [ ] 上传含 ../ 路径的 ZIP → 应拒绝
  • [ ] 上传合法的主题 ZIP → 应成功可用
  • [ ] 再次上传同名主题 → 应覆盖旧主题

1.2 Upload.php 黑名单改白名单

改动文件: app/Helpers/Upload.php

修复内容

  • 移除 $dangerousExts 黑名单(原为 php, php3, php4, php5, php7, php8, phtml, phar, cgi, pl, asp, aspx, jsp, sh, exe)
  • 改为完全依赖 $allowedTypes 白名单控制
  • .pht, .shtml, 大写 .PHP, .PhP 等黑名单未覆盖的后缀现在都会拒绝
  • MIME 校验逻辑不变

测试要点

  • [ ] 上传 .jpg 文件 → 成功
  • [ ] 上传 .php 文件 → 拒绝
  • [ ] 上传 .pht 文件 → 拒绝(黑名单时代可绕过)
  • [ ] 上传 .PHP 文件 → 拒绝(黑名单时代可绕过)
  • [ ] 上传 .shtml 文件 → 拒绝
  • [ ] 头像上传 → 正常可用

1.3 Plugin.php 任意文件包含防护

改动文件: app/Helpers/Plugin.php

修复内容

  • hook() 中 include $hookFile 前增加 realpath() 校验
  • 使用 strpos($realHook, $realPluginDir) === 0 确保钩子文件路径在插件目录内
  • 路径非法时记录 error_log 并跳过,不中断页面

测试要点

  • [ ] 安装并启用任意插件 → 插件功能正常
  • [ ] 禁用插件 → 功能不再生效
  • [ ] 修改 plugin.json 的 hooks 路径为 ../ → 被拦截(记录日志)
  • [ ] 现有插件(daily_checkin、friend_links 等)→ 钩子正常触发

1.4 SSRF — ApiController::fetchImage()

改动文件: app/Controllers/ApiController.php

修复内容

  • 原代码只校验了 URL 的 host 名字面值(如 localhost、192.168.x.x)
  • 新增 gethostbynamel() DNS 解析,对解析后的真实 IP 做私有网段过滤
  • 禁止网段:10.x.x.x、172.16-31.x.x、192.168.x.x、127.x.x.x、0.x.x.x

测试要点

  • [ ] 粘贴外网图片(如 https://example.com/image.jpg)→ 正常抓取
  • [ ] 传入 http://127.0.0.1:3306/ → 拒绝
  • [ ] 传入 http://192.168.1.1/ → 拒绝
  • [ ] 传入 DNS 指向内网的域名 → 拒绝

1.5 User.php 递归死循环 + 事务保护

改动文件: app/Models/User.php

修复内容

  • deleteWithCascade() 所有 DELETE 操作包裹在 begin/commit/rollback 事务中
  • 中途失败全盘回滚,防止产生孤儿数据
  • 递归调用问题已在之前的 SQL 一致性迁移中通过直接 $db->query('DELETE FROM threads...') 解决

测试要点

  • [ ] 删除一个普通用户 → 帖子、回复、附件全部清除
  • [ ] 删除一个管理员(且为唯一管理员)→ 检查保护逻辑
  • [ ] 模拟数据库异常断开 → 数据应回滚,不产生孤儿数据

1.6 Search.php DDL

改动文件: app/Helpers/Search.php

修复内容

  • initFTS() 只检测索引是否存在,不再自动执行 ALTER TABLE ADD FULLTEXT INDEX
  • 建索引操作剥离到后台「维护」页面手动执行
  • 缺少索引时降级为 LIKE 搜索

测试要点

  • [ ] 已有 FULLTEXT 索引 → 搜索功能正常
  • [ ] 没有 FULLTEXT 索引 → 自动降级为 LIKE,搜索结果仍有但不走全文索引
  • [ ] 后台「维护 → 重建全文索引」→ 能正常重建

回滚方式

备份文件在网站根目录的 _backup_firstround/ 下,如需回滚:

cp _backup_firstround/ThemeController.php app/Controllers/Admin/
cp _backup_firstround/Upload.php app/Helpers/
cp _backup_firstround/Plugin.php app/Helpers/
cp _backup_firstround/ApiController.php app/Controllers/
cp _backup_firstround/User.php app/Models/
cp _backup_firstround/Search.php app/Helpers/

确认一切正常后可以删除备份目录:

rm -rf _backup_firstround


第二轮修复详情

2.1 + 2.6 BaseController 二次验证 + 鉴权修复

改动文件: app/Controllers/Admin/BaseController.php

修复内容

  • 二次验证增加 30 分钟超时校验(time() - $verifiedAt > 1800)
  • 改用 parse_url($uri, PHP_URL_PATH) 精确匹配路由路径,替换原来的 strpos() 字符串匹配
  • 排除路径改为精确的 /admin/verify 和 /admin/verify/(不含查询参数)

测试要点

  • [ ] 登录后台 → 正常访问
  • [ ] 超过 30 分钟不操作 → 跳转到 /admin/verify 重新验证
  • [ ] 输入正确密码 → 恢复后台访问权限
  • [ ] 构造带查询参数的 /admin/verify URL → 不应绕过鉴权

2.2 管理员自降级保护

改动文件: app/Controllers/Admin/UserController.php

修复内容

  • 唯一管理员保护触发时加入 return 中断执行,防止 updateByAdmin() 被调用

测试要点

  • [ ] 修改唯一管理员角色为 user → 被拦截,提示错误
  • [ ] 修改非唯一管理员角色 → 正常执行
  • [ ] 修改自己的普通用户信息 → 正常执行

2.3 vote.js CSRF Token(已存在)

检查 assets/js/vote.js:

  • vote() 函数第 36 行:if (window.CSRF_TOKEN) formData.append('csrf', window.CSRF_TOKEN);
  • toggleVote() 函数第 117-119 行:同样已附带 CSRF Token

结论:无需改动


2.4 logout 退出登录(已存在)

检查 app/Controllers/AuthController.php 第 152 行:

RememberMe::clearCookies();

结论:logout() 已调用 clearCookies(),无需改动


2.5 后台 POST 补充 CSRF 校验

改动文件:

  • app/Controllers/Admin/BlogController.php — 博客分类新增/编辑 POST 分支(第 26 行)
  • app/Controllers/Admin/CategoryController.php — 版块新增/编辑 POST 分支(第 27 行)
  • app/Controllers/Admin/ThemeController.php — 各方法已有 CSRF 校验,无需改动

测试要点

  • [ ] 后台新增博客分类 → 正常
  • [ ] 后台编辑博客分类 → 正常
  • [ ] 后台新增版块 → 正常
  • [ ] 后台编辑版块 → 正常

2.7 文件删除安全

改动文件: app/Controllers/BlogController.php

修复内容

  • 删除博客时的 unlink(UPLOAD_PATH . $blog['cover_image']) 改为 unlink(UPLOAD_PATH . basename($blog['cover_image']))
  • 防止 $blog['cover_image'] 被注入 ../ 导致任意文件删除
  • ProfileController 的 unlink 已使用 realpath() 校验(无需改动)
  • Admin/UserController 的 unlink 使用 Upload 生成的纯文件名(无需改动)

测试要点

  • [ ] 删除带封面的博客 → 封面文件被删除,帖子正常删除
  • [ ] 上传新封面 → 旧封面被删除

2.8 数据库导入路径穿越

检查 app/Controllers/Admin/DatabaseController.php:

handleImport() 使用 $_FILES['backup_file']['tmp_name'](PHP 上传临时文件路径),非用户输入路径。同时校验了扩展名 .sql。

结论:攻击面有限,无需额外改动


2.9 editor.js quote DOM-XSS

改动文件: assets/js/editor.js

修复内容

  • quotePost() 函数中增加 escapeHtml() 安全转义函数
  • 对 username 和 content 进行 & < > " ' 的 HTML 实体转义
  • 防止恶意用户名(如 <img onerror=alert(1)>)在引用时触发 XSS
  • 同时修复了 Markdown 模式和纯文本 Fallback 模式下的引用安全性(使用 safeContent)

测试要点

  • [ ] 点击"引用"回帖 → 正常插入引用块
  • [ ] 用户名包含 <script>alert(1)</script> → 引用后显示为文本,不执行脚本
  • [ ] Markdown 模式下引用 → 正常显示
  • [ ] 旧浏览器 Fallback 模式 → 正常显示

回滚方式

第二轮备份在 _backup_secondround/ 目录下,如需回滚:

cp _backup_secondround/BaseController.php app/Controllers/Admin/
cp _backup_secondround/UserController.php app/Controllers/Admin/
cp _backup_secondround/BlogController.php app/Controllers/Admin/
cp _backup_secondround/CategoryController.php app/Controllers/Admin/

确认正常后可以删除备份。


第三轮修复详情

3.1 Setting.php(Model)未定义变量 $qc

改动文件: app/Models/Setting.php

修复内容

  • WHERE {$qc} = :key 中的 $qc 未定义变量,改为 WHERE \key\ = :key
  • PHP 8+ 严格模式下不再触发 Fatal Error

测试要点

  • [ ] 后台「系统设置」页面 → 修改任意设置并保存 → 成功
  • [ ] 修改站点名称 → 前台显示新名称

3.2 Content.php Base64 盲猜误判

改动文件: app/Helpers/Content.php

修复内容

  • 增加 strlen($data) % 4 === 0 校验(Base64 合法长度约束)
  • 增加 $decoded !== $data 二次校验,解码结果与原文相同时不返回解码值
  • 防止正常英文/技术文章被误解码成乱码

测试要点

  • [ ] 发一篇纯英文技术博客(含代码)→ 内容正常显示,不出现乱码
  • [ ] 发一篇含 Emoji 的中文博客 → 内容正常
  • [ ] 编辑器正常发帖 → 内容显示正确

3.3 Blog.php 无条件 Content::decode()

已由 3.2 Content::decode() 改进覆盖($decoded !== $data 校验阻止了二次解码),无需额外改动。


3.4 Vote.php IN 子句拼装错误

检查确认:$placeholders 是经 intval 强转的整数直接拼接,非 PDO 占位符。无绑定问题,安全,无需改动。


3.5 Settings.php 写缓存缺 LOCK_EX

改动文件: app/Helpers/Settings.php

修复内容

  • buildCache() 中 file_put_contents($tmp, $content) 加上 LOCK_EX
  • 防止高并发写入损坏缓存文件导致站点 500

测试要点

  • [ ] 后台保存设置 → 正常
  • [ ] 后台「维护 → 清除缓存」→ 正常

3.6 Points.php LIMIT 参数绑定问题

改动文件: app/Helpers/Points.php

修复内容

  • LIMIT :lim 改为 LIMIT ' . (int)$limit 强转后直接拼接
  • 兼容 PDO::ATTR_EMULATE_PREPARES => false 模式

测试要点

  • [ ] 积分日志页面 → 加载正常
  • [ ] 调用 Points::getLog() → 正常返回数据

3.7 附件清理误伤正在编辑的稿件

改动文件: app/Helpers/Settings.php

修复内容

  • cleanupOrphanAttachments() 中删除文件前增加 $file->getMTime() > time() - 86400 跳过
  • 24 小时内上传的临时文件不会被当成孤儿删除

测试要点

  • [ ] 上传附件但未提交帖子 → 立即执行清理 → 附件不被删除
  • [ ] 上传附件超过 24 小时且未关联 → 可被清理
  • [ ] 已关联帖子的附件 → 不受影响

3.8 高并发积分条件竞争(可延后)

当前项目规模下风险极低,暂不处理。


第四轮修复详情

4.1 sanitizeOrderBy 去反引号

改动文件: app/Core/Model.php

修复内容

  • 正则 /[^a-zA-Z0-9_.,\s\]/ 去掉反引号 \``
  • 防止列名通过反引号逃逸注入

测试要点

  • [ ] 排序相关功能(帖列表排序等)→ 正常

4.2 Captcha 验证销毁 Session

检查确认 Captcha.php 第 38 行已有 unset($_SESSION['captcha_hash'], $_SESSION['captcha_time']);,无需改动。


4.5 Theme.php 正则 \r 残留

改动文件: app/Helpers/Theme.php

修复内容

  • preg_match 正则从 /:\\s(.+?)$/mi 改为 /:\s([^\r\n]+)/i
  • 防止元数据尾部残留 \r 回车符导致比对和排版异常

测试要点

  • [ ] 后台主题管理 → 主题名称/版本信息显示正常
  • [ ] Windows 环境下上传的主题元数据 → 无尾部 \r 残留

4.7 Controller 开放重定向

改动文件: app/Core/Controller.php

修复内容

  • redirect($url) 增加 URL 校验:非本站域名的 HTTP URL 强制转为首页 /
  • 防止 $_GET['redirect_url'] 等用户输入被利用做钓鱼跳转

测试要点

  • [ ] 登录后重定向 → 正常
  • [ ] 手动传入外域 URL → 跳转到首页

4.9 Plugin 写入锁 LOCK_EX

改动文件: app/Helpers/Plugin.php

修复内容

  • 所有 file_put_contents() 加上 LOCK_EX 独占锁(共 5 处:激活、激活回滚、禁用、更新配置、缓存写入)
  • 防止高并发操作损坏 plugin.json 或缓存文件

测试要点

  • [ ] 后台启用/禁用插件 → 正常
  • [ ] 编辑插件配置 → 保存正常

完整回滚方式

# 第一轮备份
cp _backup_firstround/* app/Controllers/Admin/
cp _backup_firstround/Upload.php app/Helpers/
cp _backup_firstround/Plugin.php app/Helpers/
cp _backup_firstround/ApiController.php app/Controllers/
cp _backup_firstround/User.php app/Models/
cp _backup_firstround/Search.php app/Helpers/

# 第二轮备份
cp _backup_secondround/BaseController.php app/Controllers/Admin/
cp _backup_secondround/UserController.php app/Controllers/Admin/
cp _backup_secondround/BlogController.php app/Controllers/Admin/
cp _backup_secondround/CategoryController.php app/Controllers/Admin/

确认一切正常后删除备份:

rm -rf _backup_firstround _backup_secondround

轻量级、高性能、零 MySQL 依赖的PHP社区系统。
| 瀏覽 5 次 | 回覆 0 次

全部回覆 (0)

暫無回覆

請 登入