本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:7月5日复制的资料/安全修复计划.md。
SoSite 安全修复计划
基于全部代码审查整理,2026-07-01
覆盖范围:Controllers / Core / Helpers / Models / JS / 全局联动逻辑
总览
审查覆盖了除视图模板外的几乎所有 PHP 代码及前端 JS,共发现 约 35 个问题,按风险等级分为四档。
总工作量估计:4-6 天(每天 3-4 小时),逐项修复,每项改动量 1-30 行。
第一轮 — 🔴 致命漏洞(服务器沦陷风险)
这些漏洞一旦被利用,攻击者可以直接控制服务器或删除关键文件,建议最先修复。
1.1 ZIP 解压 RCE — Admin/ThemeController::upload()
| 项 | 内容 |
|---|
| 风险 | ZIP 解压到 Web 可访问目录后,内含的 .php 后门可直接被执行 |
| 修复 | 解压前遍历 ZIP 内文件名,拒绝包含 ../ 或 .php/.phtml/.php5 等后缀的文件;落地后仅保留白名单后缀 |
| 文件 | app/Controllers/Admin/ThemeController.php |
| 改动 | ~20 行 |
1.2 ZIP 主题孤儿目录与覆盖残留
| 项 | 内容 |
|---|
| 风险 | 同 1.1,解压中断产生无法删除的孤儿目录;同名主题覆盖后旧文件残留 |
| 修复 | 上传前检测目标目录是否存在并提示;删除时完整清理目录结构 |
| 文件 | app/Controllers/Admin/ThemeController.php |
| 改动 | 合并到 1.1 一起修 |
1.3 Upload.php 黑名单绕过可上传脚本
| 项 | 内容 |
|---|
| 风险 | 使用后缀黑名单(禁 php/phtml),但 .htaccess/.pht/.shtml/大写 .PHP 可绕过,直接上传 Webshell |
| 修复 | 改为白名单:仅允许 $allowedTypes 中声明后缀;MIME 校验改为无条件执行 |
| 文件 | app/Helpers/Upload.php |
| 改动 | ~15 行 |
1.4 Plugin.php 任意文件包含 RCE
| 项 | 内容 |
|---|
| 风险 | hook() 中 include $hookFile 无路径白名单,如果 plugin.json 被篡改可指向任意 PHP 文件 |
| 修复 | 对 $hookFile 做 basename() + 限制在插件目录内 |
| 文件 | app/Helpers/Plugin.php |
| 改动 | ~10 行 |
1.5 SSRF — ApiController::fetchImage()
| 项 | 内容 |
|---|
| 风险 | 远程拉图接口未对解析后的 IP 做私有网段过滤,可被用于内网扫描 |
| 修复 | 获取 URL 后解析 DNS 拿到真实 IP,禁止向 10.0.0.0/8、172.16.0.0/12、192.168.0.0/16、127.0.0.0/8 等私有网段发请求 |
| 文件 | app/Controllers/ApiController.php |
| 改动 | ~15 行 |
1.6 User.php 递归死循环(系统崩溃)
| 项 | 内容 |
|---|
| 风险 | deleteWithCascade() 中 $this->hardDelete($t['id']) 把帖子 ID 当用户 ID 反复删除用户自身 → 无限递归,CPU 100% 挂起 |
| 修复 | 改为 (new Thread())->hardDelete((int)$t['id']);用户级联删除包事务 |
| 文件 | app/Models/User.php |
| 改动 | ~10 行(含事务保护) |
1.7 Search.php 业务请求中执行 DDL
| 项 | 内容 |
|---|
| 风险 | initFTS() 中 ALTER TABLE ADD FULLTEXT INDEX 在前端请求中执行,大表时会导致全表锁死 |
| 修复 | 将 DDL 操作剥离到安装脚本或后台 CLI 维护命令中,前端只检测索引是否存在 |
| 文件 | app/Helpers/Search.php |
| 改动 | ~10 行 |
第二轮 — 🟠 高危漏洞(数据安全 / 越权 / 功能瘫痪)
这些漏洞不会直接导致服务器沦陷,但可能导致数据泄露、越权操作、功能瘫痪或关键逻辑绕过。
2.1 后台二次验证被完全绕过(无超时)
| 项 | 内容 |
|---|
| 风险 | BaseController 只检查 admin_verified_at 存在性,不检查超时。管理员在公共电脑上权限永久开放;且二次验证状态未与 user_id 绑定 |
| 修复 | BaseController::__construct() 中加超时校验(30 分钟过期),验证状态绑定当前 user_id |
| 文件 | app/Controllers/Admin/BaseController.php |
| 改动 | ~15 行 |
2.2 管理员自降级保护逻辑绕过
| 项 | 内容 |
|---|
| 风险 | Admin/UserController 编辑唯一管理员时虽然触发了错误检查,但没有中断执行,updateByAdmin() 依然被执行 → 唯一管理员可把自己变为普通用户,后台锁死 |
| 修复 | 检测到唯一管理员保护时 return 中断,不执行更新 |
| 文件 | app/Controllers/Admin/UserController.php |
| 改动 | ~5 行 |
2.3 vote.js 缺 CSRF Token → 点赞不可用
| 项 | 内容 |
|---|
| 风险 | FormData 未携带 CSRF Token。后端需要 CSRF 校验,所有点赞请求被拒绝 → 点赞功能全站瘫痪。如果后端为迁就前端关闭校验,则暴露在跨站请求伪造下 |
| 修复 | 从页面 <meta name="csrf-token"> 提取 Token 加入 FormData |
| 文件 | assets/js/vote.js |
| 改动 | ~5 行 |
2.4 退出登录后 RememberMe 自动重新登录
| 项 | 内容 |
|---|
| 风险 | logout() 只销毁了 $_SESSION,未调用 RememberMe::clearCookies()。刷新后 RememberMe 检测到 Cookie 仍在,自动登录回来 → 用户永远无法真正退出 |
| 修复 | logout() 中加上 RememberMe::clearCookies() |
| 文件 | app/Controllers/AuthController.php |
| 改动 | 1 行(已有 clearCookies 方法,检查是否被调用) |
2.5 多处后台 POST 缺 CSRF 校验
| 项 | 内容 |
|---|
| 风险 | Admin/BlogController、Admin/CategoryController、Admin/ThemeController 的保存/修改 POST 分支缺少 CSRF 校验 |
| 修复 | 在各 POST 分支入口加 Csrf::verifyOrDie() |
| 文件 | 3 个 Controller |
| 改动 | 每处 ~3 行 |
2.6 strpos 鉴权绕过
| 项 | 内容 |
|---|
| 风险 | strpos($uri, '/admin/verify') 可通过 URL 构造绕过 |
| 修复 | 改为基于当前路由/控制器名的白名单放行 |
| 文件 | Admin/BaseController.php |
| 改动 | ~10 行 |
2.7 任意文件删除(多处 unlink 无 basename)
| 项 | 内容 |
|---|
| 风险 | BlogController::delete()、Admin/MaintenanceController、Admin/UserController 中的 unlink() 未对路径做白名单校验 |
| 修复 | 所有 unlink 前用 basename() 提取文件名,或封装统一的安全删除方法 |
| 文件 | 3 个文件 |
| 改动 | 每处 ~5 行 |
2.8 数据库导入路径穿越
| 项 | 内容 |
|---|
| 风险 | handleImport() 未对导入文件名做 basename() 过滤 |
| 修复 | $safeFile = $backupDir . basename($inputFile) |
| 文件 | Admin/DatabaseController.php |
| 改动 | 1 行 |
2.9 editor.js quote DOM-XSS
| 项 | 内容 |
|---|
| 风险 | 引用功能中 username 和 content 未转义直接拼入 HTML。恶意用户可将用户名改成 <img onerror=alert(1)>,任何引用此人的用户都会触发 XSS |
| 修复 | 拼接前对 username 和 content 做 HTML 转义(替换 < > " ' &) |
| 文件 | assets/js/editor.js |
| 改动 | ~10 行 |
第三轮 — 🟡 重要缺陷(功能异常 / 数据一致性问题)
这些问题不会直接产生安全漏洞,但会导致功能异常、数据损坏或体验严重下降。
3.1 Setting.php(Model)未定义变量 $qc
| 项 | 内容 |
|---|
| 风险 | $qc 未定义,在 PHP 8+ 严格模式下触发 Fatal Error,后台设置页面无法保存 |
| 修复 | 改为 WHERE \key\ = :key |
| 文件 | app/Models/Setting.php |
| 改动 | 1 行 |
3.2 Content.php Base64 盲猜误判
| 项 | 内容 |
|---|
| 风险 | 仅凭正则盲猜 Base64,正常英文/技术文章可能被误判解码成乱码 |
| 修复 | 改为前端通过特定字段标识是否 Base64,或增加固定前缀 |
| 文件 | app/Helpers/Content.php |
| 改动 | ~15 行 |
3.3 Blog.php 无条件 Content::decode()
| 项 | 内容 |
|---|
| 风险 | Model 层对每篇博客无条件调用 Content::decode(),配合上述缺陷,正常文章变乱码 |
| 修复 | 不在 Model 层解码,由 Controller 按需决定 |
| 文件 | app/Models/Blog.php |
| 改动 | 删掉 decodeRows/decodeRow 调用 |
3.4 Vote.php IN 子句拼装错误
| 项 | 内容 |
|---|
| 风险 | $placeholders = implode(',', $ids) 再以单占位符传入 PDO,结果是 IN ('1,2,5') 只匹配一条 |
| 修复 | 动态生成对应数量的占位符逐一绑定,或直接拼接已 intval 强转的数字 |
| 文件 | app/Helpers/Vote.php |
| 改动 | ~10 行 |
3.5 Settings.php 写缓存文件缺 LOCK_EX
| 项 | 内容 |
|---|
| 风险 | buildCache() 写入无独占锁,高并发写入损坏缓存文件 → 站点 500 |
| 修复 | file_put_contents($file, $content, LOCK_EX) |
| 文件 | app/Helpers/Settings.php |
| 改动 | 1 行 |
3.6 Points.php LIMIT 参数绑定问题
| 项 | 内容 |
|---|
| 风险 | LIMIT :lim 使用 PDO 命名占位符,EMULATE_PREPARES=false 时无法绑定 |
| 修复 | 改为 (int)$limit 直接拼接 |
| 文件 | app/Helpers/Points.php |
| 改动 | 1 行 |
3.7 附件清理误伤正在编辑的稿件
| 项 | 内容 |
|---|
| 风险 | cleanupOrphanAttachments() 会把已上传但未关联 thread_id 的附件(用户正在编辑中)当作孤儿删掉,导致发帖后图片全裂 |
| 修复 | 只清理创建时间在 24 小时前仍未关联的附件 |
| 文件 | app/Helpers/Settings.php |
| 改动 | ~10 行 |
3.8 高并发积分条件竞争
| 项 | 内容 |
|---|
| 风险 | Points::award() 无事务保护,高并发投票时积分可能不一致 |
| 修复 | 加分逻辑包事务或使用行级锁 |
| 文件 | app/Helpers/Points.php |
| 改动 | ~5 行 |
| 建议 | 当前项目规模下风险极低,可暂缓处理 |
第四轮 — 🟢 建议优化(性能 / 规范 / 体验)
4.1 sanitizeOrderBy 允许反引号
| 项 | 内容 |
|---|
| 风险 | 当前正则 /([^a-zA-Z0-9_.,\s\])/` 允许了反引号,列名可被注入 |
| 修复 | 去掉反引号,改为 /([^a-zA-Z0-9_.,\s])/ |
| 文件 | app/Core/Model.php |
4.2 captcha 验证成功未销毁 Session
| 项 | 内容 |
|---|
| 风险 | 验证成功后未 unset(),180 秒窗口内可被重放 |
| 修复 | verify() 返回 true 前销毁 Session 标记 |
| 文件 | app/Helpers/Captcha.php |
4.3 Tag.php getAll() 全表扫描性能
| 项 | 内容 |
|---|
| 风险 | LEFT JOIN + GROUP BY + ORDER BY count DESC 在大数据量时无法利用索引 |
| 修复 | tags 表冗余 thread_count 计数器字段 |
| 文件 | app/Helpers/Tag.php |
4.4 Category.php GROUP BY 性能
| 项 | 内容 |
|---|
| 风险 | 每次首页加载都执行全表 COUNT + GROUP BY,百万级数据时数秒返回 |
| 修复 | categories 表冗余 thread_count/post_count 字段 |
| 文件 | app/Models/Category.php |
4.5 Theme.php 正则 \r 残留
| 项 | 内容 |
|---|
| 修复 | 改为 /:\s*([^\r\n]+)/i 排除回车换行 |
| 文件 | app/Helpers/Theme.php |
4.6 登录锁定基于 Session 可被绕过
| 项 | 内容 |
|---|
| 修复 | 锁定状态基于用户名存数据库(当前已有验证码作为第二道防线) |
| 文件 | app/Controllers/AuthController.php |
4.7 Controller 开放重定向
| 项 | 内容 |
|---|
| 修复 | redirect() 对 URL 做 parse_url() 检验是否本站域名 |
| 文件 | app/Core/Controller.php |
4.8 弱类型比较
多处 == 改为 ===,建议顺手修。
4.9 LOCK_EX 写入锁(PluginController)
Plugin::updateConfig() 写入 plugin.json 时加 LOCK_EX。
执行顺序建议
第一轮(致命 7 项) → 2 天
├── 1.1 ZIP RCE + 孤儿目录
├── 1.2 Upload 白名单
├── 1.3 Plugin 文件包含
├── 1.4 SSRF
├── 1.5 User 递归死循环 + 事务
└── 1.6 Search DDL
第二轮(高危 9 项) → 1.5 天
├── 2.1 二次验证 + 超时
├── 2.2 管理员自降级
├── 2.3 vote.js CSRF
├── 2.4 logout 不退出的 Bug
├── 2.5 后台 CSRF 补漏
├── 2.6 strpos 鉴权
├── 2.7 文件删除安全
├── 2.8 数据库导入路径
└── 2.9 editor.js quote XSS
第三轮(重要 8 项) → 0.5 天
├── 3.1 Setting $qc
├── 3.2 Content Base64
├── 3.3 Blog decode
├── 3.4 Vote IN 子句
├── 3.5 LOCK_EX
├── 3.6 Points LIMIT
├── 3.7 附件清理误伤
└── 3.8 积分条件竞争(可延后)
第四轮(优化 9 项) → 顺手修
4.1 ~ 4.9 规范/性能
涉及文件清单(去重后)
| 层 | 文件 |
|---|
| Controllers | Admin/ThemeController、Admin/UserController、Admin/BaseController、Admin/BlogController、Admin/CategoryController、Admin/DatabaseController、Admin/MaintenanceController、ApiController、AuthController |
| Core | Controller、Model |
| Helpers | Upload、Plugin、Settings、Content、Points、Captcha、Search、Tag、Theme |
| Models | User、Setting、Blog、Category |
| JS | assets/js/vote.js、assets/js/editor.js |
总计约 20 个文件,每项改动 1-30 行,全部修完预计 4-6 天。
我的想法
- 第一轮 6 项必须修 — ZIP RCE、Upload 白名单、Plugin 文件包含、SSRF、User 递归死循环、Search DDL。任何一个被利用都会导致服务器出大问题。
- 第二轮 9 项建议修 — 最值得注意的新问题是 管理员自降级绕过(唯一管理员可把自己变为普通用户→后台锁死)和 logout 退不出登录(用户体验级 Bug)。vote.js 缺 CSRF 导致点赞瘫痪也是刚需。
- 第三轮建议修 — Setting.php 的
$qc 导致后台设置无法保存、Content.php 的 Base64 盲猜导致正常文章变乱码,这是用户能直接感知的问题。附件清理误伤也挺坑的。
- 第四轮可以缓缓 — 性能优化在数据量上来再做;弱类型比较不会产生实际安全问题。
- 总体评价 — 修完这批问题后,SoSite 的安全性和稳定性会非常扎实。主要集中在"写了但没中断"、"写了但忘了消费"这类细节疏忽上,没有架构层面的硬伤。