本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:备忘md/security_review_report.md。
SoSite V3 安全与逻辑审查报告
审查日期:2026-06-21
审查范围:100+ PHP 文件(Core / Controllers / Models / Helpers / Views)
审查方法:逐文件阅读,追踪数据流(Source → Sink),检查安全边界
总体评分:良好 ✅
整体代码质量较高,安全基座扎实。项目普遍使用参数化查询、CSRF 令牌、htmlspecialchars() 输出转义、密码哈希、文件上传校验等标准安全实践。
1. 安全亮点
| 项目 | 覆盖情况 |
|---|
| SQL 注入防护 | 全部使用参数化查询(:param),无字符串拼接 SQL |
| XSS 防护 | 模板引擎 {$var} 自动 htmlspecialchars,e() 方法统一转义 |
| CSRF 保护 | 所有 POST 修改操作均校验 csrf 令牌 |
| 密码安全 | password_hash() + PASSWORD_DEFAULT,password_verify() 恒定时间比较 |
| 文件上传 | MIME 校验 + 扩展名白名单 + 危险扩展拦截 |
| 速率限制 | 登录锁定(5 次/120s)、注册(3 次/h)、投票(30 次/min)、上传(5 次/min) |
| 会话安全 | httponly、SameSite=Lax、session_regenerate_id() 登录后 |
| 内容过滤 | Content::sanitizeHtml() HTML 白名单过滤,禁止 javascript:/data: 协议 |
2. 发现的问题
问题一:Search FTS 同步使用字符串内联 SQL(低风险)
| 项目 | 内容 |
|---|
| 文件 | app/Helpers/Search.php — syncThreadFTS() L182-L185、syncBlogFTS() L205-L208 |
| 代码 | $conn->exec("DELETE FROM threads_fts WHERE rowid = {$threadId}") 使用了字符串内插而非参数绑定 |
| 描述 | 虽然 $threadId 已被 (int) 强制类型转换,SQL 注入不可行,但这是不安全的编码模式,未来维护者可能复制此模式到其他场景 |
| 建议 | 改为参数化绑定 |
问题二:highlightTerms() 双编码 + @ 抑制错误
| 项目 | 内容 |
|---|
| 文件 | app/Helpers/Search.php — highlightTerms() L152-L158 |
| 描述 | 视图层调用 $this->highlightSearchTerms($this->e($result['title']), $keyword) 时传入已转义文本,函数内部再次 htmlspecialchars() 导致双编码。@preg_replace 抑制错误 |
| 风险 | 极低。preg_quote 转义了搜索关键字,注入不可行。@ 仅隐藏错误 |
| 建议 | 去掉视图层多余的 $this->e()(函数内部会转义),移除 @ 错误抑制 |
问题三:Admin 删除操作通过 GET 传递 CSRF Token
| 项目 | 内容 |
|---|
| 文件 | app/Controllers/Admin/ThreadController.php — L10-L13 |
| 描述 | 使用 GET 请求 + URL 中的 csrf token 执行删除。CSRF Token 在 URL 中可能通过 Referer 头泄露 |
| 风险 | 低。仅限管理员登录后操作,Referer 泄露在同站点场景下风险有限 |
| 建议 | 改为 POST 请求或至少使用 $_SERVER['REQUEST_METHOD'] 校验 |
问题四:设置项无输入验证
| 项目 | 内容 |
|---|
| 文件 | app/Controllers/Admin/SettingsController.php — L12-L17 |
| 代码 | $settingModel->setValue($key, $_POST[$key] ?? '') — 直接存储用户输入 |
| 描述 | site_name 等设置项未做长度/内容校验,XSS 过滤依赖视图层 e() 转义 |
| 风险 | 低。管理员可管理后台,理论上已有完全权限 |
| 建议 | 增加长度限制和 HTML 标签剥离 |
问题五:$_SESSION['role'] 直接存储未经校验
| 项目 | 内容 |
|---|
| 文件 | app/Controllers/AuthController.php — L62-L63 |
| 代码 | $_SESSION['role'] = $user['role'] ?? 'user',直接从数据库取值存入 Session |
| 风险 | 极低。前提是数据库已被攻破,此时问题已超出此范围 |
| 建议 | 可选:在 Auth::isAdmin() 中每次都查询数据库而非仅读 Session |
3. 逻辑评审
| 问题 | 说明 |
|---|
| Vote 积分奖励条件 | ApiController::vote() 中 $result['user_vote'] 在 toggle() 返回的是本次投票的值,逻辑始终正确。但比较用 == 而非 ===,建议统一 |
| 登录锁定 IP 盲区 | AuthController::login() 的锁定基于 Session,攻击者可通过清空 Cookie 或更换 IP 绕过。建议结合 IP 进行锁定 |
| RememberMe Token 无轮换 | 每次登录生成新 Token,但没有在使用"自动登录"后轮换 Token(防重放)。建议实现 Token Rotation |
| HTML 模板缓存安全 | TemplateCompiler 将编译后的模板写入缓存目录。PHP 模板中若包含用户控制的 {$var:raw} 输出,原始内容可能被写入缓存文件。当前代码中 raw 仅用于信任数据,风险极低 |
4. 总结
| 项目 | 值 |
|---|
| 审查文件数 | 100+ PHP 文件 |
| 安全评分 | 82 / 100 |
| 直接可利用漏洞 | 0(HIGH / MEDIUM / LOW) |
| 防御层面建议 | 5 项(均为低风险,详见第 2 节) |
代码库整体安全性良好,没有可直接利用的漏洞。所有数据交互均使用参数化查询、输出正确转义、关键操作受 CSRF 保护、敏感操作有速率限制和登录验证。上述 5 项问题均为防御纵深层面的建议,可分批优化。