到时候会发布完整增量包和完整安装包,请及时更新。
update/20 改动说明(精简版)
版本:V1.0.20 · 状态:D 批进行中,尚未冻结 覆盖:95 个代码文件 + 改动说明.md,共 96 个文件 验收:765 条断言 / 0 失败 + HTTP 冒烟 28/0 + 镜像 23/23 MATCH
一、批次总览
| 批次 |
主题 |
项数 |
| A |
安全止血 |
8 |
| B |
机制修复 + 数据一致性 |
14 |
| C |
契约断裂专项 |
9 + 全项目扫描 |
| D |
批量硬化(D1~D9) |
约 60 |
二、A 批:安全止血(8 项)
| 编号 |
问题 |
改法 |
| S-1 |
净化器对 <style>/<script> 的 CDATA 节点完全失效 → 存储型 XSS |
白名单循环前整块 removeChild 6 类 raw-text 元素 + 输出侧 CDATA 节点走 htmlspecialchars() |
| H-4 |
搜索 type 无白名单 → 反射型 XSS(分页 URL 未转义) |
pagination() 三出口统一转义(含 $prevHref/$nextHref)+ $type 白名单 |
| H-3 |
/api/upload 省略 category_id 即绕过 can_attach |
抽出 canUploadAttachment() 默认拒绝;显式版块须真实存在 + 权限判定;/api/fetch-image 复用同判定 |
| H-2 |
users.status 全链路不校验 → 封禁完全失效 |
Auth::getCurrentUser() / RememberMe::check() / AuthController::login() 三处统一判 status !== 'active' |
| V-M10 |
?msg[]=x 数组参数触发 TypeError → 500 |
index.php 入口剥离 $_GET 非标量项;TemplateCompiler::query() 作为视图唯一入口 |
| M-1 |
APCu 未启用 → 每请求刷错误日志(178 条) |
新增 apcuUsable() 真实探测;flush() 拆 APCu 只读段与 DB 写入段 |
| A7 |
默认密钥硬编码并随包分发 |
支持环境变量 / protected/*_key.php 覆盖 + *_IS_DEFAULT 标记;后台告警条 |
| A8 |
幽灵路由 /admin/database/convert 必现 500 |
删除两条路由 |
不做:MAIL_PASS_KEY / CRON_KEY 轮换(config 本地不一致,会解不开存量 SMTP 密文)
三、B 批:机制修复(14 项)
| 编号 |
问题 |
改法 |
| H-5 |
计数器系统性漂移(实测 70 vs 72) |
deleteWithCascade 补状态过滤 + Post::softDelete 下沉维护 reply_count + Settings::runtimeGet 兜底改只读 |
| H-1 |
跨库写路径零事务(extern / 桶 / 索引三步非原子) |
统一写入顺序 + 每步失败即时补偿(@unlink + DELETE),把静默分裂降级为可观测失败 |
| H-8 |
外置正文存储锁不一致 + 缓存不失效 |
ExternMerger 复用 $idxPath.lock;写 .idx 后统一 apcu_delete |
| H-6 |
5 个后台控制器审计零记录 |
UserController/PluginController/GroupController/TrashedController/CategoryController/DatabaseController(备份下载)共 13 处补 AuditLog::log |
| H-7 |
插件 system:settings 权限可绕过 |
Setting::setValue() 下沉校验 + debug_backtrace 反查插件来源 + 补齐 5 处漏传 |
| M-2 |
RateLimiter 检查-写入非原子 + 无锁读 |
收进同一 LOCK_EX 临界区;json 损坏保守拒绝;cron 清理 7 天陈旧缓存 |
| M-5 |
sanitizeOrderBy 黑名单 / update() 返回 PDOStatement / insert() 无列白名单 |
改解析式白名单 + 返回受影响行数 + PRAGMA table_info 列白名单 |
| M-7 |
Permission 无记录默认放行 → 新版块隐蔽越权 |
默认拒绝,仅整表为空时放行 |
| M-9 |
改密后旧会话与「记住我」不失效 |
ProfileController 改密后 RememberMe::clearTokens() |
| M-10 前半 |
点赞可无限刷积分(TOCTOU) |
条件写 + rowCount() 判定首次 |
| M-11 |
归档只读限制仅 show 有惰性标记 |
抽 lazyMarkArchived() 在 edit 前调用 |
| M-6 |
回帖事务横跨插件 hook / 通知 / 上传 |
事务只包业务表写入,钩子/通知/上传移出 |
| M-1 后半 |
ViewCounter 并发重复落库 |
apcu_cas 抢占 + COMMIT 后 apcu_delete |
| 低危 |
CSRF 无时效判定 + csrf[]=x 触发 500 |
verify() 补时效 + 非标量兜底;config.php 补 display_errors=0 |
不做:M-10 后半 points_log 唯一索引(会破坏回帖/骰子/邀请码等正常重复业务)
四、C 批:契约断裂专项(9 项 + 1 次扫描)
特征:页面上看着正常,实际保存后回落到未筛选状态、字段被悄悄改写、或整块结果永远空白。
| 编号 |
问题 |
改法 |
| V-M06 |
权限矩阵保存后回跳丢 category_id → 页面回落空矩阵 |
回跳携带 category_id |
| V-M08 |
插件编辑保存静默丢弃 category |
控制器透传 |
| V-M09 |
自动扩容阈值每次保存被改写为 1 |
改为「字段存在才写」(视图按设计不渲染该字段) |
| V-M13 |
博客封面无法移除(控制器有消费、视图无入口) |
补复选框 + 三语键 |
| V-M02 |
搜索「用户」标签被无条件 continue,永远空白 |
补用户结果卡片分支 |
| V-M03 |
搜索高亮入参预先 e() → 双重转义 |
4 处调用改传原始文本 |
| V-M14/L19 |
分页丢筛选参数(blog 归档 / search 时间范围) |
补参数 + rawurlencode |
| V-M11 |
messages 无 username 列,视图键名不对齐 → 收发件人恒为空 |
模型别名对齐 + 详情页补齐 |
| V-M15 |
密码不合规时 tokenValid 变 false → 表单被收起 |
改为 (bool)$row |
C0 扫描:scan_form_contract.php → ①「控制器读 $_POST 但无视图渲染」= 0 项(退出码 0);沉淀为发布前检查项。
五、D 批:批量硬化(D1~D9)
D1 BASE_PATH 家族(11 文件)
| 问题 |
改法 |
视图/渲染层直读 REQUEST_URI 未剥离部署前缀 → 子目录部署下布局/导航/侧栏整体错乱 |
抽 TemplateCompiler::currentPath(),收敛 9 处直读;偏好 cookie path 随 BASE_PATH |
D2 <script>/CSS 上下文口径(12 文件)
| 问题 |
改法 |
<script> 内误用 HTML 转义(浏览器先实体解码再 JS 解析)/ json_encode 缺 JSON_HEX_* / CSS 值上下文注入 |
新增 TemplateCompiler::cssValue();JS 上下文统一 json_encode + JSON_HEX_* |
D3 i18n(5 文件)
| 问题 |
改法 |
logs/audit.php 全页硬编码中文;缺 admin.queue_mode_switch_confirm;plugins/xiuxian/lang/ 缺 zh_tw.php |
三语各 1318 键(1302→1318) |
D4 可用性/不可逆操作(6 文件)
| 问题 |
改法 |
| 主题设置表单无提交按钮、全靠 JS;数据库危险页无常驻警示、确认全依赖 Alpine |
<noscript> 兜底提交按钮;危险页常驻警示 + 服务端 CONFIRM 硬拦截 |
D5 内容完整性(6 文件)
| 问题 |
改法 |
Content::decode() 非幂等 + 视图二次解码 → 正文静默改写 |
解码职责归位模型;修正 Thread.php 两处错误注释;引用框改用已解码值 |
D6 缓存(2 文件)
| 问题 |
改法 |
| 任一用户切私有偏好即清空全站静态页缓存;PageCache 落盘非原子 |
Theme::set() 加「值真变化才清」;落盘改 tmp + rename |
D7 模板引擎(1 文件)
| 问题 |
改法 |
safeExpr() 只检查第一个函数调用 → isset($x) && system("id") 放行 |
preg_match_all 遍历全部 + 完整标识符比对(当前无可达注入点,属回归封堵) |
D8 服务端校验(12 文件,6 新增)
| 问题 |
改法 |
标题/正文长度仅前端 maxlength;LIKE 降级搜索缺 ESCAPE;通知聚合从渲染后文案正则抓数字;建表 DDL 进程内重复执行 |
新增 Content::lengthError()(7 写入点);LIKE_ESCAPE 常量;notifications.agg_count 列;PRAGMA user_version + 进程内缓存 |
D9 低危收尾(分组)
组 1:L2 BASE_PATH 守卫 / L16 /resend-verification 补 POST 路由 / L19 CSP 补 3 条 / L29 登录时序侧信道 / L32 json() 脏数据 500 / L33 开放重定向补拒反斜杠 / L17 $categoryId 定义顺序
组 2:L14 分页页码钳制 / L25 标签去重 + 上限 + uk_tags_name 唯一索引 / L15 改邮箱预检
组 3:L3 三处 GET 即写操作 → POST + CSRF(/layout-toggle、通知已读、重建统计)
组 4:L6 归档桶只读(PRAGMA query_only + 11 处维护路径旁路)+ 归档竞态 ③.5 复检 / L7 删除路径容错 + 补偿回滚 / L8 活跃桶缺失不再静默建库
组 5:L10 模型层缓存加 500 上限 + 修 array_shift 键重置缺陷 / L11 busy_timeout 还原 / L21 Settings 对账加值比对 / L24 UserPrefs::clear()
组 6:L5 内置组拒绝改名 / L12 验证码降级盐随机化 + /api/captcha 限流 / L18 站点名去双重转义 / L20 首页前置缓存键对齐 / L22 备份路径 isWithin() + zip 白名单 / L23 Imagick 魔数闸门 / L26 钩子 include 作用域隔离 / L27 主题名白名单 / L31 closeAll() 释放句柄
L9 + L28:残留桶目录备份后清理 + 桶文件集合自检;SessionHandler 实现 validateId / updateTimestamp,use_strict_mode 真正生效
六、不做的事(已决策)
| 项 |
决策 |
理由 |
MAIL_PASS_KEY / CRON_KEY 轮换 |
不做 |
config 本地不一致,会解不开存量 SMTP 密文 |
| M-12 索引优化 |
不做 |
36 万数据实测无问题 |
| M-10 后半唯一索引 |
不做 |
会破坏回帖/骰子/邀请码正常业务 |
| B2 第 2 步(wal_journal) |
本包不做 |
先上最小补偿,按序分两次发布 |
L34 sw.js CACHE_VERSION |
待确认 |
递增会强制全站访客重拉静态资源 |
| L12 后半 PoW 难度 |
待确认 |
影响低端设备登录耗时 |
| 架构调整 |
不做 |
按 Q6 只做扫描 |
七、部署要点
- 覆盖 95 个代码文件(含
version.php),包内共 96 文件
config.php 禁止直接覆盖:须手工合并 A7 密钥段 + B7 display_errors 两行(见附)
- 清
data/runtime/pages/*.html:D9 组 3 改了导航输出,旧缓存含 href="/layout-toggle?next=…",游客点击会 404
- 后台点一次「维护 → 重建统计」对齐
_runtime_total_*
- D8
notifications.agg_count 列 / D9 组 2 uk_tags_name 索引:首次访问自动补齐,无需迁移脚本
data/.htaccess 不再由 zip 导入还原:今后用后台导入备份时若缺该文件需手工放置
- L9 残留桶清理:本包只含自检代码,生产站如有同类残留须先比对
bucket_path 全集、备份后再清理
八、验收数据
| 批次 |
断言数 |
失败 |
| A |
11 项 |
0 |
| B |
~250 |
0 |
| C |
55 + 11 + 11 + 扫描 |
0 |
| D1~D8 |
~400 |
0 |
| D9 组 1~6 |
~400 |
0 |
| L9 + L28 |
57 |
0 |
| 合计 |
765 |
0 |
- 镜像校验:95 文件站点侧 ↔ 包内 SHA-256 全部一致
- HTTP 冒烟:
smoke_http_c1 11/0、smoke_http_g3 17/0
- 编码:全部 UTF-8 无 BOM + 纯 LF
- 三语 i18n:各 1326 键、集合完全一致
九、已知限制(诚实披露)
- 无 APCu / 无 pcntl:M-1 并发分支、H-8 并发合并、M-2 真并发、B6 并发穿透 —— 本机无法构造真并发,需 Linux + 生产规模副本复验
- BASE_PATH 后台设置仍不生效:本批只消除 Warning;真正生效需把常量改运行时变量(架构调整)
- CSP 仍含
unsafe-inline / unsafe-eval:移除需改造全站内联脚本(架构级)
- 归档竞态仍有毫秒级残留窗口:彻底消除需跨文件事务
Content::decode 注释已纠正为「非幂等」:历史数据可能含 base64 态
updateTimestamp 行不存在时回退 write():兼容 gc 中途清行场景,正常路径仍只一条 UPDATE