[开发]FlintHub 双模型交叉裁决报告

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

本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:暂存20260913/FlintHub_双模型交叉裁决报告_20260904.md。

FlintHub 双模型交叉裁决报告

  • 裁决日期: 2026-09-04
  • 裁决对象:
  • 报告 A(Qwen):FlintHub代码审计报告.md(深度代码审计,H1~H6 / M1~M5 / L1~L6)
  • 报告 B(MiMo):FlintHub_深度架构审计报告_20260904.md(跨层架构审计,H-1~H-5 / M-1~M-4 / L-1~L-3)
  • 核实方式: 逐条对照源码(含控制器/模型/SplitDB/Helper/插件 20+ 文件),并结合已实施的 P0/P1/P2/P3 批次修复记录排除"已修复"项。
  • 备注: 用户最初提到的 FlintHub代码漏洞审计报告.md 为空模板(821 字节,各节均为"待补充"),无内容可供裁决,已按更正后的两份报告执行。

一、双模型确认的问题(合并去重,标注双模型确认)

两份报告独立命中的同一根因,合并为一条。

1. [双模型确认] 搜索降级路径读退役表 threads(Qwen M1 = MiMo H-1)

  • 文件:行号: app/Helpers/Search.php:312-321(FROM threads t 的 COUNT 与分页查询)
  • 问题: SplitDB 迁移后 business.threads 已退役(P4 起不再写入,实测 0 行),但 searchLIKE() 降级路径仍 FROM threads t。倒排索引异常触发降级时,帖子搜索结果对新帖恒为空。
  • 双模型表述差异: Qwen 侧重"读退役表导致结果恒空";MiMo 侧重"搜索在 SplitDB 后遗漏新帖"。本质同一问题,合并。
  • 补充: 该问题与 Qwen H4(:since 崩溃)位于同一批 SQL,须一并修复(见 P0-2)。

2. [双模型确认] 附件清理读退役表 → 误删在用附件(Qwen M2 = MiMo H-2)

  • 文件:行号: app/Helpers/Settings.php:613-617(cleanupOrphanAttachments 的 $contentTables)
  • 问题: 用 SELECT content FROM threads/posts WHERE content LIKE :p 收集正文内嵌图片以保护在用附件,但 threads/posts 均为退役空表 → 分片后主题/回复正文里的图片 URL 扫不到 → 后台"清理孤儿附件"会把仍被正文引用的上传文件误删(数据丢失)。blogs 未分片,保留有效。
  • 双模型表述差异: Qwen 侧重"误删在用附件(数据丢失)";MiMo 侧重"附件清理逻辑扫描遗漏新内容"。同一问题,合并。

二、仅单份报告提出、DS 已核实的问题

Qwen 单独提出(逐条核实)

编号文件:行号结论裁决
H1app/SplitDB/ExternStorage.php:48,156,166,182,270属实。缓存键 extern_{id} 只用裸 ID;IDGenerator.php:35 中 topic/reply 两条独立自增序列(均从 1 起)→ ID 区间重叠,启用 APCu 后读主题 5 可命中回复 5 的正文缓存(跨内容串读)。当前主机未装 APCu,属休眠态。P0
H2app/SplitDB/ExternStorage.php:30-61、app/SplitDB/ShardRouter.php:123-128、app/Models/Post.php:66、app/Models/Thread.php:58属实。extern 路径 extern/{Y}/{Q}/{桶}/{id}.txt 用裸 ID:主题桶=id%32,回复桶=父帖桶(Post::insert 取父帖 bucket_path);当父帖桶==R%32 且同季度时,主题 R 与回复 R 写入同一文件路径,atomicRename 静默互相覆盖、delete 互相误删。当前小数据未命中属概率侥幸,规模化必现。P0
H3app/Models/User.php:388-489属实。$db->begin()/commit() 仅包裹 business 库,分片桶/main_index/ExternStorage 文件删除均在事务外(代码注释自认)。中途异常时跨库不一致且无补偿。属多文件 SQLite 架构固有短板。注:删除用户"他人主题下回复残留"部分已由 P1-2 批次修复(:440-468 兜底清理已存在)。P1
H4app/Helpers/Search.php:313,315,321,334属实且当前可触发。searchLIKE() 无条件绑定 ':since',但 SQL 仅 $since !== '' 时才含 :since 占位符;默认 timeRange='all' → $since=''。本工程 DBFactory.php:69 已强制 ATTR_EMULATE_PREPARES=false,原生预处理下多余命名参数抛 SQLSTATE[HY000] column index out of range → 降级路径自身 500。帖子与博客两条查询均受影响。P0
M3app/Models/User.php:427-429属实但不构成当前可利用漏洞。删除附件文件未做 Thread::hardDelete(:655)同款 preg_match('/^[a-zA-Z0-9_.\-]+$/') 白名单。但 attachments.filename 仅来源于 app/Helpers/Upload.php:82 的 bin2hex(random_bytes(16)).ext,无法含 ../。属防御一致性缺口。P2
M4app/SplitDB/Schema.php:786-803属实。bucketFromPath 归档桶文件缺失时静默 mkdir + ensureBucketTables 重建空表并返回连接,读取变"查不到=空",掩盖归档文件丢失。该问题豆包复核报告 L-04 已发现、至今未修,非本轮新增。P1
M5app/Core/Model.php:104-119属实但潜伏。基类 update() 仅做键名字符校验、无列白名单,属批量赋值面。当前未发现调用方把 $_POST 原样传入基类 update(),但任何新调用点直接传请求数组即可改 role/group_id/status/points。P2
L1app/Core/Model.php:106-117属实。$data 为空或全部键被过滤时 $sets='',拼出 UPDATE "t" SET WHERE ... 语法错误。豆包 P1-6 建议过、P1 批次实际未实施,现码仍无防护。P2
L2app/Core/Model.php:189-200属实。buildWhere 数组分支全非法键时 return '1'(全表匹配)。注:字符串分支已由 P2-① 改 fail-closed,数组分支是剩余的不一致点。P2
L3app/Core/Model.php:133-143属实。count() 传数组条件时 $conditions !== '' 成立但 is_string 为假 → 拼出 WHERE Array。当前无此类调用方,属死路径。P2
L4app/SplitDB/IDGenerator.php:47-96属实(可用性提示)。busy_timeout=5000ms × 最多 3 次重试,高写争用单请求最坏约 15s 阻塞;取号原子性本身正确。P2
L5app/Helpers/PageCache.php:42-49,107误报。报告称游客 lang 值可制造无界缓存文件,但 I18n::detect()(I18n.php:206-210)对 Cookie/URL/Session 全部要求 in_array($l, available())(仅已安装语言包),非法语言一律回落默认,缓存键有界,无法制造无界文件。误报
L6app/SplitDB/ExternStorage.php:69-81误报(目标运行环境不成立)。目标为 PHP 8.2.9,rename() 可直接覆盖已存在目标,atomicRename 首分支必然成功,unlink+rename 兜底分支不会执行(该分支已由 P2-⑥ 统一抽离)。误报

MiMo 单独提出(逐条核实)

编号文件:行号结论裁决
H-3app/Helpers/Plugin.php:112-121属实。permission_enforce 默认 '0'(警告模式),未声明权限的插件仍放行 net:fetch/route:admin/system:settings。属安全模型默认策略偏松,非当前被利用漏洞。P2
H-4plugins/mod_system/Plugin.php:377-380,405-408属实。封禁/解封直接 UPDATE users SET group_id,绕过 User Model 旁路逻辑(缓存/审计)。当前无实际破坏(group_id 变更即生效),属架构一致性。P2
H-5plugins/red_packet/Plugin.php:89-92属实。创建红包直接 UPDATE users SET points = points - :p,绕过 Points::deduct() 的条件原子扣减(AND points >= :p)与 points_deduct_after 钩子。事务内先读后减存在窄竞态面。P2
M-113 个插件直查核心库 users 表属实。均为读操作 SELECT id, username FROM users WHERE id IN (...),安全风险低,属架构规范问题。P2
M-2plugins/mod_system/AdminController.php:270-271属实。原生 DELETE FROM blog_comments 绕过 Blog Model 级联。影响有限(博客评论无独立计数列需递减)。P2
M-3plugins/seo/plugin.json:16属实。activate() 调 Settings::update 写全局设置,但 permissions 仅 ["route:admin"] 缺 system:settings;仅 permission_enforce=1 时才受影响。P2
M-4plugins/notifications/plugin.json:9属实但当前无害。permissions 为空数组 [],但未注册 admin_route_register,现有钩子不受限。P2
L-1plugins/seo/FrontController.php:33误报(代码卫生)。未使用实例变量,无安全影响。误报
L-24 处 file_get_contents误报。均为本地固定路径读取,非网络请求,不涉及 SSRF;code_repo 已有 realpath 边界校验。误报
L-3app/Helpers/Search.php:313并入 P0-2。即"搜索未走分片索引全文匹配",与 Qwen M1/H4 修复方向重叠,不单列。并入


三、最终修复清单

必须修(P0)

P0-1 主题/回复 extern 命名空间隔离(H1 + H2 合并)

  • [app/SplitDB/ExternStorage.php:48,156,166,182,270](H1 APCu 键串读)
  • [app/SplitDB/ShardRouter.php:123-128](H2 .txt 路径互相覆盖)
  • 问题:主题与回复共用裸 ID 的 APCu 缓存键与 .txt 文件名,ID 区间重叠导致跨内容串读/互相覆盖/误删。APCu 未启用时休眠,启用或数据上量后必然触发正文损坏。
  • 修复建议(必须具体):
  1. ExternStorage::write/read/delete 增加 type 参数('topic'|'reply'),APCu 键改为 extern_t{id} / extern_r{id};所有调用方(Thread::insert/update/hardDelete、Post::insert/update/softDelete、User::deleteWithCascade、Thread::batchDelete/deleteRepliesOf)同步传入。
  2. ShardRouter::externRel() 增加 type 参数,文件名改为 t{id}.txt / r{id}.txt(目录可共用)。
  3. 存量迁移:新增 cli/migrate_extern_namespace.php——遍历 data/extern/**/*.txt,按 main_index 判定归属:id 存在于 topic_index 且桶号==路径桶号 → 主题,重命名 t{id}.txt;否则存在于 reply_index → 回复,重命名 r{id}.txt;两者皆无则记日志待人工核对(防歧义误判)。迁移后删除旧文件。

P0-2 搜索降级路径修复(H4 + M1 合并)

  • [app/Helpers/Search.php:312-321](M1 读退役表恒空)
  • [app/Helpers/Search.php:313,315,321,334](H4 多余 :since 崩溃)
  • 问题:倒排索引异常触发 searchLIKE() 降级时,帖子查询读退役 threads 恒空;且默认 timeRange='all' 下多余 :since 绑定参数在 ATTR_EMULATE_PREPARES=false 下抛错,帖子与博客两条查询均 500,安全网完全失效。
  • 修复建议(必须具体):
  1. 仅当 $since !== '' 时才把 ':since' 加入绑定数组(帖子 COUNT、博客 COUNT、帖子列表、博客列表 4 处同步改),或始终在 SQL 保留 :since 并用 AND (:since = '' OR created_at >= :since)。
  2. 帖子分支去掉 FROM threads t:改读 main_index.topic_index(title LIKE :q + status=0 AND deleted_at IS NULL + category 白名单 + 时间过滤),列表行用 Thread::getByIds() + Thread::fillExternContent() 补正文/用户名/分类(与已修好的索引路径同口径);降级仅覆盖标题全文、正文匹配明确声明为降级能力(或复用 SearchIndexStore)。
  3. 移除 t.content LIKE 与 content_snippet 对 threads 的依赖。

建议修(P1)

P1-1 删用户跨库非原子(H3)

  • [app/Models/User.php:388-489]:begin/commit 仅包 business,桶/main_index/文件在事务外;中途失败产生跨库不一致且无补偿。
  • 修复建议:异常路径把未完成清单($threadIds/$replyIds)写入 error_log + 审计表并抛"可重跑"提示;或改分阶段幂等收敛(先桶/索引/文件,最后删用户行,用户行删除失败时记待清理清单供后台重跑)。

P1-2 归档桶缺失静默建空表(M4)

  • [app/SplitDB/Schema.php:786-803]:归档桶缺失静默重建空表,掩盖归档丢失,前台静默显示空帖。
  • 修复建议:bucketFromPath 区分 active/archive——active 首次写入可建;archive 缺失抛 RuntimeException 或 error_log 计数告警,不静默返回空连接。

P1-3 附件清理误删在用附件(M2,双模型确认)

  • [app/Helpers/Settings.php:613-617]:threads/posts 退役表扫不到分片正文内嵌图,后台清理可能误删仍在用的上传文件。
  • 修复建议:删除 threads/posts 两条查询;受保护文件名改从 main_index.topic_index.excerpt_images(已落库的正文图片列)+ blogs.content 收集;如需覆盖回复正文图片,可在低频后台任务中遍历活跃桶 reply.extern_path 读取匹配(性能可接受)。

可以后置(P2)

  • [app/Models/User.php:427-429](M3)附件文件删除补 basename() + 与 Thread::hardDelete 同款 preg_match('/^[a-zA-Z0-9_.\-]+$/') 白名单。
  • [app/Core/Model.php:104-119](M5)基类 update() 增加 $fillable 列白名单,越界键忽略并记日志。
  • [app/Core/Model.php:106-117](L1)$sets === '' 时返回 0 / 抛明确异常,避免非法 SQL。
  • [app/Core/Model.php:198](L2)buildWhere 数组分支全非法键改抛异常(fail-closed),与字符串分支一致。
  • [app/Core/Model.php:133-143](L3)count() 非字符串条件显式走 buildWhere 或抛类型错误。
  • [app/SplitDB/IDGenerator.php:47-96](L4)适当下调 busy_timeout/重试次数,对取号超时埋点告警。
  • [app/Helpers/Plugin.php:116](MiMo H-3)生产环境将 permission_enforce 置 1,或在框架层对 net:fetch/system:settings 默认强制。
  • [plugins/mod_system/Plugin.php:377-380,405-408](MiMo H-4)封禁/解封改走 User Model 统一封装(新增 User::ban()/unban())。
  • [plugins/red_packet/Plugin.php:89-92](MiMo H-5)积分扣减改走 Points::deduct()/award() 统一封装。
  • 13 插件直查核心库 users 表(MiMo M-1)新增 User::batchGetNames() 封装 + 请求级缓存。
  • [plugins/mod_system/AdminController.php:270-271](MiMo M-2)博客评论删除改走 Blog Model 封装方法。
  • [plugins/seo/plugin.json:16](MiMo M-3)permissions 补 "system:settings"。
  • [plugins/notifications/plugin.json:9](MiMo M-4)如需后台入口补 "route:admin",否则保持空并在文档标注。

误报(忽略)

  • [app/Helpers/PageCache.php:42-49,107](Qwen L5):I18n::detect() 已用 in_array(available()) 将语言限定为已安装语言包,缓存键有界,无磁盘耗尽 DoS。
  • [app/SplitDB/ExternStorage.php:69-81](Qwen L6):目标 PHP 8.2+ rename() 直接覆盖,unlink+rename 兜底分支不会执行。
  • [plugins/seo/FrontController.php:33](MiMo L-1):未使用变量,纯代码卫生。
  • 4 处 file_get_contents(MiMo L-2):均为本地固定路径读取,非 SSRF。

四、与豆包/DS 已修复项的重叠核对

已对照 P0/P1/P2/P3 批次修复记录逐条排除,本轮清单不含任何已修复项:

  • 附件下载权限(P0-1)、模板 RCE(P0-2/P0-3)、Dashboard 读退役表(P1-1)、删用户他帖回复残留(P1-2)、Category fallback(P1-3)、旧 extern 文件清理(P1-4)、User::search LIKE 转义(P1-5)——均已修复,两份报告均未重复提出。
  • P2/P3 已修项(buildWhere 字符串分支、Search 索引路径 N+1、ViewCounter、DBFactory、Upload finfo、ExternStorage atomicRename 统一等)中,仅有两处"相邻但未完全覆盖"的残余:
  • buildWhere 字符串分支已 fail-closed,但数组分支(L2)仍是残余不一致点;
  • searchLIKE 的 N+1 未在 P2-③ 范围内(该批只改了 searchByIndex 索引路径),故降级路径的 H4/M1 仍待修。
  • 豆包已发现但尚未修复(本轮予以确认并列入):Schema bucketFromPath 归档桶静默建空表(=Qwen M4,豆包 L-04)、Model::update 空集(=Qwen L1,豆包 P1-6)。

五、结论

  • 双模型确认 2 条:搜索降级读退役表(M1/H-1)、附件清理读退役表(M2/H-2)——两份报告唯一实质重叠区,均属实。
  • Qwen 独有且最有价值:H1/H2(主题/回复裸 ID 命名空间冲突,正文串读/覆盖,休眠但必现)、H4(:since 崩溃)为豆包/DS 未覆盖的新发现,是本次裁决最重要的产出。
  • MiMo 独有项:全部为架构一致性/权限策略类(P2 级),无当前可利用漏洞;L-1/L-2 为误报。
  • 误报共 4 条:L5、L6、L-1、L-2,均已给出反证。
  • 优先级建议:先 P0-1(数据损坏风险,启用 APCu/扩容前必须落地 + 存量迁移)与 P0-2(搜索安全网),再 P1-1~P1-3,其余 P2 按批次排期。
轻量级、高性能、零 MySQL 依赖的PHP社区系统。
| 浏览 2 次 | 回复 0 次

全部回复 (0)

暂无回复

请 登录