[修复]FlintHub 合并安全审计与修复清单

👑Lv.11 元老 🌞 骨干会员
2026-10-25 00:24:03

本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:暂存20260913/合并安全审计与修复清单.md。

FlintHub 合并安全审计与修复清单

交叉验证来源:mimo静态审计报告.md(mimo-v2.5-pro)+ GLM静态审计.md

交叉验证时间:2026-09-09

交叉验证方式:逐条回读真实源码,比对「报告所述文件名/行号」与「代码现状」;对报告中已存在防御逻辑的条目标注为「误报」并附代码证据。

纪律:本任务只做合并与交叉验证,未修改任何代码。


P0 必须修复(交叉确认)

两份报告各自独立指向同一条根问题,且都给出「入口 → 汇」完整链路。这是唯一重合项,必须修复。

1. P0 — base_path 设置项零校验 → 全站 JS 持久 XSS + 开放重定向(mimo #4 ∩ GLM M-2 重合)

  • 重合依据:
  • mimo #4(P1):SettingsController.php 将 base_path 从 $_POST 直写数据库,无格式校验。
  • GLM M-2(MEDIUM):同 SettingsController.php:24-27 为存储点,指标 main.php:66。

```php

foreach ($keys as $key) { // $keys 含 site_name/base_path/site_lang

$settingModel->setValue($key, $_POST[$key] ?? '');

}

```

仅校验 CSRF(L22),未对 base_path 做任何格式/字符校验。

  • 代码核实 — 传播点(真实):init.php 将库内 base_path 直接 define('BASE_PATH', $bp)。
  • 代码核实 — JS 字符串汇点(真实,未转义):main.php

```php

window.BASE_PATH='<?php echo defined('BASE_PATH') ? BASE_PATH : ''; ?>';

```

该值原样写入单引号 JS 字符串,未走 $this->e() / json_encode()。当 base_path 为 x';alert(document.cookie)// 时 ' 直接逃逸字符串 → 每页持久 XSS。

  • 代码核实 — 重定向汇点(真实):Controller.php redirect() 在 URL 已以 / 开头且非 // 时追加 BASE_PATH 并 Location:,若 base_path='//evil.com' 则所有重定向跳外域。
  • 触发前提:需要有效管理员会话 + CSRF(GLM 的提权路径:仅劫持会话、未过二次验证时,借任一同域注入点写坏 base_path → 先取得持久 XSS → 挂钩 /admin/verify 表单窃取二次密码 → 完整管理员)。
  • 修复方案:服务端对 base_path 强制白名单 ^\/[A-Za-z0-9_\-\/]*$(禁 //、反斜杠、引号),非法值拒绝/回退空串;输出侧 main.php:66 改用 json_encode(BASE_PATH) 包裹。

P1 建议修复(单报告命中、经核实确认真实可修复)

1. P1 / HIGH — 后台标签管理存储型 XSS(GLM H-1,mimo 未报)

  • 位置:app/Views/admin/tags.php:39
  • 代码核实(真实):onclick 内 JS 单引号字符串 prompt(..., '<?php echo $this->e($tag['name']); ?>')。$tag['name'] 仅经 $this->e()(htmlspecialchars)输出,但该值处于 onclick="..." 双引号属性内 → 浏览器先做实体解码,&#039; 还原为 ' 再交给 JS 引擎 → 逃逸字符串。
  • 源 → 汇:任意注册用户创建标签名(Tag.php saveForThread 仅 trim+长度 1~30,无字符白名单,原文入库)→ tags 表 → 管理员打开后台标签页点击「重命名」触发。
  • 证据对照:同文件 tags.php:42 删除按钮采用 htmlspecialchars(json_encode(...)) 正确姿势,L39 是漏网之笔 —— 印证误用而非刻意为之。
  • 触发前提:需管理员一次点击交互(非纯被动),脚本以管理员身份执行。
  • 修复方案:改与 L42 同款 —— htmlspecialchars(json_encode($tag['name']), ENT_QUOTES, 'UTF-8');另建议在 Tag::saveForThread 对标签名加字符白名单。

2. P1 / MEDIUM — 重复点赞重复发积分(积分/等级体系可刷)(GLM M-1,mimo 未报)

  • 位置:app/Controllers/ApiController.php:81-87 + app/Helpers/Vote.php:172-186
  • 代码核实(真实):Vote::toggle 用 ON CONFLICT(...) DO UPDATE SET vote=:vote2(L180),重复点赞时 UPSERT 仍"成功"且恒返回 user_vote=$vote(L186);ApiController.php:81 仅判 $vote==1 && user_vote==1 即 Points::award(L84),无首次状态迁移判定、无 (投票者,目标) 去重。
  • 可利用性:双小号循环「点亮→取消→再点亮」每次给目标发分;限流 30 次/分只减速不去重,可刷积分/等级及等级关联权限。
  • 修复方案:Vote::toggle 返回"是否由非赞态首次变为赞态"(对比 UPSERT 前后实际行为差),仅状态迁移时发分;并对 (voter, target) 维度发分去重。

3. P1 / MEDIUM — SMTP 加密集硬编码默认值随源码分发(GLM M-3,mimo 未报)

  • 位置:config.php:32(GLM 报 L31,实测为 L32)
  • 代码核实(真实):define('MAIL_PASS_KEY', 'SoSite_AES_Key_2026!@#XyZ7890AbCdEf=') 为固定默认值随源码分发。FlintHub 为分发型程序,未改密钥的部署共享同一密钥;任何能读到源码的途径(git 泄露/备份/未来任意文件读)即可结合 Mailer::decryptSmtpPass 还原 settings 表中的 SMTP 授权码明文。
  • 修复方案:密钥移出代码库(环境变量或 protected/ 下 Web 不可达的独立密钥文件),安装时随机生成;发布升级公告引导存量站点轮换密钥并重加密存量 SMTP 密码。

4. P1 / LOW — lang 切换 PRG 回跳开放重定向(GLM L-3,mimo 未报)

  • 位置:app/init.php:121-131
  • 代码核实(真实):?lang= 白名单通过后(L123-124),回跳 Location: 直接取原始 REQUEST_URI 仅剔除 lang 参数(L127-130),未拒绝 // 开头。诱导访问 https://站点//evil.com/?lang=zh-cn → 回跳 Location: //evil.com/ → 跳外域,可作钓鱼跳板。
  • 修复方案:回跳前仅取 parse_url($uri, PHP_URL_PATH) 并拒绝 // 前缀。

5. P1 / LOW — /api/upload 绕过 can_attach 版块上传策略(GLM L-2,mimo 未报)

  • 位置:app/Controllers/ApiController.php:126-149
  • 代码核实(真实):upload() 仅校验 POST + 登录 + CSRF(L131-136)+ 限流(L141),直接调 Upload::file(L142),完全不校验 Permission::canAttach。被设为各版块 can_attach=0 的用户(发帖/回帖/编辑路径均被拦截)仍可直传白名单文件并获外链,绕过按版块上传策略及磁盘占用滥用。
  • 修复方案:纳入统一上传权限体系,上传前校验目标版块 can_attach。

6. P1 / LOW — /api/vote 不校验目标版块可见性(GLM L-1,mimo 未报)

  • 位置:app/Helpers/Vote.php:163-170(经 ApiController.php:66-75 调用)
  • 代码核实(真实):toggle 前仅校验目标存在于 topic_index/reply_index(L166-168),未按目标 category_id 调 Permission::canView,软删目标亦不排除。登录用户可对无权浏览版块内容投票;配合上述重复点赞可给隐藏内容刷积分/形成存在性预言机。
  • 修复方案:toggle 前补 canView + status 校验。

7. P1 / LOW — /admin/verify 二次验证无限速(GLM L-4,mimo 未报)

  • 位置:app/Controllers/Admin/VerifyController.php:18-28
  • 代码核实(真实):仅 Csrf::verifyOrDie(L19)+ password_verify(L22),无 RateLimiter/锁定/PoW(对比登录有双层限速)。前提是攻击者已持有有效管理员会话 + 页面 CSRF(会话劫持场景),此时可高速爆破二次验证密码突破最后一道防线。
  • 修复方案:复用 RateLimiter(如 5 次/15 分钟)并在连续失败后使会话失效。

已忽略(误报 / 理论建议)

原报告条目忽略原因(含代码证据)
mimo #1 SSRF IPv6 绕过 isBlockedIp/isBlockedHost[误报/理论] isBlockedIp/isBlockedHost(ApiController.php:267-280)确实缺 IPv6 段,但实际入口带有效防护:gethostbynamel()(L194)仅返回 IPv4 记录。字面 [::1]、[fd00::]、IPv4-mapped ::ffff: 均无法经 gethostbynamel 解析出 IP → 返回 false/空 → 直接拒绝(L195-198/L203);纯 AAAA 记录域名同样得到空结果被拒。报告所述「AAAA 指向 fd00::1」场景在 gethostbynamel 下不可达。属加固缺口、非可证实利用。
mimo #2 SSRF GET 端点无 CSRF[理论] ApiController.php:166-176 仅判断登录态且无 CSRF,但其响应是 JSON 返回受害浏览器而非攻击者侧,主要价值是「借已登录用户触发 SSRF」——依赖 #1 的 IPv6 绕过成立才成链,而 #1 已被 gethostbynamel 实效拦截。单独不构成可利用闭环。
mimo #3 ZIP 解压符号链接任意覆写[理论] DatabaseController.php extractZipToData:776-786 确未检 symlink 条目,但导入/恢复均为管理员本人经 POST+CSRF 触发(L816/L862);需管理员主动导入恶意压缩包,属社会工程/管理员自伤,边际影响不大于管理员已有的 SQLite 导入能力。
mimo #5 模板 {!! !!} 原样输出[理论] TemplateCompiler.php:145 的 cbRaw 确不转义,但报告自述"当前视图中未发现对用户输入使用 {!! !!} 的情况",纯未来/规范建议,无现存触发点。
mimo #6 SQLite 导入仅校验魔术字节[理论] DatabaseController.php handleImport:887-895 确仅验文件头,但导入同样为管理员本人经 POST+CSRF 触发(L862),恶意库等于管理员主动种下,非外部可利用入口。
mimo #7 后台恢复 GET 触发状态变更[理论] MaintenanceController.php:58-79 GET 分支确无 CSRF,但动作是重建统计计数(total_*)非破坏,且 $pendingFile 仅在管理员刚发起 runtime_rebuild 后短暂存在;报告自述主要担心是"错误范式"而非实际危害。
mimo #8 safeReferer 允许 localhost[理论/低危] ApiController.php:650-661 白名单含 localhost/127.0.0.1,但影响需同机/同源,仅上传成功回跳,无实际开放重定向闭环,属加固建议。
mimo #9 fetchHopByHop 禁用 SSL 验证[理论/加固] ApiController.php:306-309 / 347-348 确 verify_peer=false,却是 IP-replacement 防 DNS Rebinding 的必要副作用(连 IP 时证书主机名不匹配);后续有 MIME 白名单(L241-243)+ 大小上限兜底。Imagick 侧也已 setOption('svg:auto-graphic','false')(Upload.php:163)。
mimo #10 上传扩展名白名单可配[误报/理论] Upload.php:25-28 白名单读后台,但其后有硬编码 MIME 映射表强制约束(Upload.php:61-79),后台把 php 加入白名单也会因 php 不在 $allowedMimes 中被拒;报告自述"当前安全",仅未来若扩展映射表才需再防。
GLM 低置信:Controller::redirect 未拦 /\ 变体[理论] Controller.php:97 仅拒非 / 与 //,/\evil.com 未拦,但当前调用方均传服务端构造路径,无端到端可控入口(GLM 自估 0.72 未达标上报门槛)。
GLM L-5 uploads 无 .htaccess/web.config 禁执行[理论/加固] config.php:22 上传目录在 Web 根且内无禁执行文件,但白名单+MIME 硬约束当前使 .php/.svg 无法落盘,不可直接利用;仅迁移到 Apache/IIS 或配置漂移时才有洞,属纵深建议。
GLM L-6 RememberMe 失配审计可伪造[低危] RememberMe.php:38-41 确可用 remember_user_id=<受害者> + 假 token 无鉴权写入失配审计,但仅污染日志(IP 取 REMOTE_ADDR 无法冒名),非直接被利用漏洞。
GLM L-7 User::findByUsername/findByEmail 返回全行[理论] User.php:19-27 确返回 *(含 password hash),但 GLM 自述"未证实透出到响应",属最小取数规范建议,无实际泄露链路。
GLM 存疑:BaseController 未 use Csrf 致批量转移 500[功能缺陷非安全] BaseController.php:56 调 Csrf::verifyOrDie 但文件头(L9-12)未 use app\Helpers\Csrf,命名空间下解析为不存在的类 → 帖子/博客批量转移会 500;fail-closed 安全不受影响,仅功能不可用,顺手修复。
GLM 存疑:DatabaseController mapBackupToTarget 前缀校验[理论] strpos($realTarget,$realRoot) 未加尾分隔符可兄弟目录前缀命中,但利用需管理员+导入 CSRF+目标已存在+限 .sqlite,危害边际。
GLM 存疑:ExternStorage read/delete 汇点无路径校验[理论] 当前全部 extern_path 写入点均为 write() 内部生成、无外部入口;仅未来新增导入功能才成任意文件读写,属预留治理。
GLM 存疑:search_settings.php:43 $rebuildNext 未转义[理论] 来源为服务端拼接(int + urlencode token)无用户可控链路(SettingsController.php:467)。
GLM 存疑:custom_css 过滤可绕过无汇 / Model:219 ORDER BY 盲注 / CSP unsafe-inline[理论] 均无实际可达汇点或当前为配置偏弱,属加固备注。


分级总结

等级数量说明
P0 必须修复1base_path 零校验 → 全站持久 XSS + 开放重定向(mimo #4 ∩ GLM M-2 交叉确认)
P1 建议修复7标签 XSS(H-1)、重复点赞刷分(M-1)、SMTP 密钥(M-3)、lang 开放重定向(L-3)、上传绕过 can_attach(L-2)、投票不校验可见性(L-1)、二次验证无限速(L-4)
忽略21误报/理论/加固(详见上表)

说明:P0 仅 base_path 一项为两模型精确指认同一根问题的交叉确认。GLM 偏重业务逻辑/转义姿势,mimo 偏重 SSRF/管理后台,二者整体交集有限,其余条目经代码核实后按严重度计入 P1 或判为理论/误报忽略。

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

全部回覆 (0)

暫無回覆

請 登入