🔐 安全:附件 API 与私有存储
附件 API GET 越权枚举修复:Bearer 身份及所有者/管理员授权,白名单返回安全元数据,禁止泄露真实存储路径与 URL;下载响应
private/no-store。补充 Apache 压缩包及临时元数据拒绝规则、Nginx/CDN 上线处置说明和回归脚本。私有附件默认位置调整:改为站点目录内
upload/private/(0700,自动创建)。原默认dirname(APP_PATH)/.xiunox-private-*在 Web 根之外,被线上open_basedir拦死导致protect()throw、下载页 500。root()仅拒绝「私有目录 = Web 根本身」,子目录放行;强保护仍可用conf/private_storage.php/XIUNOX_PRIVATE_PATH指到 Web 根外。代价为弱保护:upload/.htaccess加 RewriteRule 禁止/upload/private/直链,Nginx 需加location ^~ /upload/private/ { deny all; }。PrivateAttachmentStorage::isPrivate():读路径捕获存储不可用异常降级false(私有文件公开存储无副本,最坏 404 不泄露;写路径仍 throw 防静默落公开存储);root()异常消息补充open_basedir/XIUNOX_PRIVATE_PATH排查指引。迁移工具 CLI 启动修复:
tool/migrate_private_storage.php原因 xiunophp 不加载conf/conf.php导致$conf为空、$db为 NULL 无法启动;现自行 include conf.php、转绝对路径、加载 version.php,补EscapeService/model/user.func.php,lib 类改class_exists/include_once(修HiddenContentGuard重复声明 fatal)。本地 dry-run 通过(扫描 48 / 需保护 14)。在线升级链路加固:
OnlineUpgradeService开启 TLS 证书校验,解压前逐条目检查../、绝对路径、盘符穿越。4 处高危存储型 XSS 封堵:API 发帖/编辑标题补
strip_tags;API 签名 JSON body 补xn_signature_purify+ 长度校验;API 昵称 JSON body 补htmlspecialchars;appcenterdescription/self_check_report入库前xn_html_purify。json_ld输出加JSON_HEX_*+strip_tags封</script>逃逸;search 页标题先esc_html再打 mark;置顶列表与 moments 卡片补esc_html。xnx_hidden 五处泄露出口封堵:搜索摘要、帖子页 SEO description 与 og:image、xnx_theme_moments 列表摘要与卡片首图、xnx_fcomment 侧栏共享缓存改存 pid 按查看者渲染、xnx_ai_reply 送第三方 LLM 前过滤(新增
lib/HiddenContentGuard+ 回归脚本)。API 越权四处修复:
thread/hot补版块读权限;PUT /user/{uid}改邮箱/改密须验当前密码并补邮箱唯一性;mod move(API+Web)补目标版块管辖校验;like/favorite/report 对不可读帖统一 404。框架层加固:
db_orderby_to_sqladd键名与 maxid 字段名加标识符白名单(双拷贝同步);post 回收站 LIKE 查询迁db_sql_find_prepared;my_notify链接esc_attr;AttachmentService::saveToSession同步max(array_keys)+1修复并发 key 覆盖。其他加固:待审头像文件名加随机段(防时间戳可枚举直链);
xnx_setting_transfer导入暂存 JSON 迁私有存储;审核队列回帖摘要过ApiContentService::hidden。第二轮对抗性复核补修:API PUT /thread 标题补长度≤80 + 敏感词检查 + 被驳回帖编辑后重进待审;API 改签名移除并改指
PUT /my/signature;nickname 审核分支补notify_new_pending+ 计次;admin.php 日志筛选命名占位符改?位置绑定;base64 头像上传$destfile未定义修复;appcenter 后台导入description/version_changelog入库前xn_html_purify;pwa.js CSRF cookie 名修正。
⚡ 修复/性能:LIKE 通配符转义
新增统一 helper
xn_like_escape()(转义\_%,\排首位;db.func.php与xiunophp.min.php双拷贝同步),修复 8 处裸 LIKE 拼动态值:route/user.php@用户搜索、admin/route/log.php日志用户检索、admin/route/thread.php后台帖子搜索+回收站筛选、model/thread.func.php thread_find_by_keyword、api/v1/search.php三类搜索、api/v1/thread.php帖子列表 keyword、model/attach.func.php附件文件名检索。MySQL 缓存驱动
deleteByPrefixLIKE 未转义:前缀_被当单字符通配符致几乎全表扫描(每次发帖/删帖清版块缓存都卡顿),且同长度异形键可能被误删。改str_replace一次性转义(\必须在首位),双拷贝同步。
📊 计数修复与统计口径
审核通过链路守卫:
AuditService单条/批量 approve 的 thread 分支跳过is_deleted;post 分支跳过isfirst与is_deleted并校验父主题存在且未软删;批量通过的作者通知从「全部入参」收紧为「实际通过项」。主题通过时同步首帖
post.audit_status(单条+批量):此前只更新 thread 表,导致已通过主题的楼主正文对普通用户隐藏。post_restore拒绝父主题已软删/不存在的回帖;回收站「评论」列表收敛为「活主题下软删回帖 + 孤儿回帖」。分页总数与列表同口径:首页「关注/精华」、用户主页/用户中心主题分页补
is_deleted=0;「我的回复」「我的主题」分页总数改实时 COUNT。thread.likes永久虚高修复:新增post_like_delete_by_pids/post_like_delete_by_tid,接入 5 条硬删路径;软删/恢复保留点赞行保持对称。user_purge补清理被删用户发出的点赞/收藏行,逐帖递减。精华计数软删对称:
thread_soft_delete/restore补递减/回补user.digests/forum.digests/runtime.digests,硬删加is_deleted守卫防二次扣减。runtime_init补初始化digests,负数自愈条件补 digests。移动精华帖补
thread_digest_update同步 fid 快照。批量幂等过滤:
thread_soft_delete_batch/thread_restore_batch补入库状态过滤(soften_tids/restore_tids),返回值改为实际处理数。attach_delete补删后按 pid 重算images/videos/files。post_delete_by_uid补维护thread.posts/lastpid,按 tid 汇总递减 + lastpid 重算 + 缓存失效。后台「重新计数」扩展:新增
thread.posts全量重建;补thread.likes对账post_like、thread.favorites对账thread_favorite、user.digests/forum.digests/runtime.digests对账可见精华主题数、post.likes每楼点赞对账;失效活跃用户榜缓存。
🚀 删帖性能与索引
post_delete_by_tid重写:db_find分批(500/批)+ 列裁剪避开 longtext,附件分批 unlink + 一次 IN DELETE,回帖本体走KEY(tid,pid)一次批量 DELETE,一并物理清除历史遗留软删回帖附件(记thread_delete_perf日志)。post_delete_by_tids_batch重写:tid 按 100/批 IN 查询 + 列裁剪;附件 pid 分 500/批走 IN DELETE。post_delete_by_uid重写:分批扫描 + 列裁剪,补齐此前从未清理的用户回帖附件。thread_delete/thread_delete_batch补thread_top/thread_digest硬删清理 + 按 uid/fid 聚合递减 digests 统计。AttachAccessService::deletePermissionByTid批量化(500 附件 = 500 SQL → 一次 IN DELETE)。bbs_attach新增KEY tid,UpgradeService::upgradePerfIndexes()幂等迁移idx_tid(探测两个候选索引名避免重复)。admin/route/thread.phpop=delete/hard_delete 循环改走thread_delete_batch(批删 SQL 从 8×N 降到 6 常数次)。
🔌 插件级联
为 10 个插件补齐
model_thread_delete_batch_start/endhook:xnx_attach_access(_start)、xnx_hidden / xnx_poll / xnx_related / xnx_private_reply / xnx_comment_visibility / xnx_reward / xnx_fields / xnx_lottery / xnx_tag(_end)。修复后台批删/回收站硬删仅调用thread_delete_batch但无插件订阅该 hook,导致投票/悬赏/抽奖/话题/自定义字段等业务表数据永久残留。xnx_reward 批末一次性
CacheHelper::pluginDeletePrefix,避免每 tid 触发 Redis SCAN;xnx_comment_visibility 批末单次 clearCache。
🔧 编译器与 hook
hook 编译兜底守卫漏洞修复:旧
strpos('// hook')守卫与编译正则不等价,//HOOK x、//hook x、// hook x等变体逃逸改写仍被二次展开,致 hook 内容线性拼接、同作用域 static 重复声明 fatal(实案线上view_htm_post_list.inc.htm帖子页 500)。改为无条件preg_replace,任何可匹配形态一律改写为// hook:NAME。.htmhook 归因注释按占位符上下文区分:PHP 上下文(// hook xxx.htm源标记)用裸行注释 + 内容剥离 PHP 标签,HTML 上下文(<!--{hook xxx}-->源标记)保持 PHP 标签包装,防嵌套开标签 ParseError。实案修复 xnx_tag 话题标签致发帖页post.htm:48全站 ParseError(thread-create 白屏)。xnx_reward v1.1.8:修复帖子页 500
Duplicate declaration of static variable $xnx_reward_post_data——post_list_inc_userinfo_actions_after.htm与thread_list_inc_subject_after.htm改用isset守卫的普通变量替代 static 缓存(宿主模板同一 hook 占位符出现两份时编译产物会拼接两份 hook,PHP 8 编译期即 fatal);同作用域普通变量在 foreach 迭代间天然保持值,语义等价,重复拼接时第二份直接跳过。
🔍 线上排查记录
open_basedir未含私有目录致 500:open_basedir(/www/wwwroot/xiunox.org/:/tmp/)未含/www/wwwroot/.xiunox-private-*,致attach_format→isPrivate→root()throw,含附件帖子页 500(服务器侧扩充open_basedir解决)。xnx_reward static 重复声明 fatal 复发观察:20:10/21:58 同
view_htm_post_list.inc.htm:288,ErrorHandler 对 tmp fatal 无条件 unlink → 复发 = 每次重建仍烘出重复 → 服务器文件集与本地不一致;本地编译试验台实测当前组合产物 static 恰 1 处php -l过,待服务器四条判别命令定位差异源。
🧩 插件版本
插件 | 版本 | 主要变更 |
|---|---|---|
xnx_feeds | 1.4.4 | 点赞动态跳转 404 修复(按评论维度 extra.tid + 楼层锚点);楼层锚点格式 |
xnx_fcomment | 1.0.2 | 楼层锚点格式对齐全站约定 |
xnx_reward | 1.1.8 | static 缓存改 isset 守卫普通变量,修复帖子页 500 |
xnx_tag | 1.1.4 | 后台设置页分页点击无效修复(URL 补 |
xnx_poll | 1.1.2 | 后台投票管理页 404 修复(段式路由 + |
xnx_demo_data | 1.0.1 |
|
oulin_hongbao | 1.0.2 | 三个 PHP hook 文件头注释改规范 |
xnx_appcenter | 1.15.40 | — |
xnx_hidden | 1.1.8 | — |
xnx_pwa | 1.0.4 | — |
navgcn_ticket | 1.2.4 | — |
xnx_setting_transfer | 1.0.1 | — |
xnx_theme_moments | 1.0.2 | — |
xnx_fcomment | 1.0.1 → 1.0.2 | — |
xnx_ai_reply | 1.0.5 | — |
xnx_attach_access | 1.2.4 | — |
xnx_related | 1.0.2 | — |
xnx_private_reply | 1.0.1 | — |
xnx_comment_visibility | 1.0.2 | — |
xnx_fields | 1.0.4 | — |
xnx_lottery | 1.3.11 | — |
📚 文档与后台
后台「设置→固定链接」Nginx 伪静态片段补充
location ^~ {base}/upload/private/ { deny all; }(宝塔伪静态 + 自建 server 各一处,子目录安装自动带 base_path);Caddy 片段补@xiunox_private path+respond 403。docs/install.md3.1.1 伪静态规则、3.1.2/7.4 完整示例同步补充该 deny;3.2 Apache 说明upload/.htaccess已内置禁止直链。online-runbook 与迁移工具说明同步更新;
.gitignore忽略upload/private/*。
⚠️ 升级提醒
私有附件策略变更:默认位置改为
upload/private/,需在 Nginx 配置location ^~ /upload/private/ { deny all; },否则受保护附件可被直接下载;已有站点建议运行tool/migrate_private_storage.php迁移存量。上线后需清理
tmp/编译缓存并刷新 OPcache(涉及route/model为_include编译文件)。后台需执行一次「重新计数」 修复存量计数漂移(本地验证:user.posts 漂移 11 人、user.threads 2 人、thread.posts 41 帖)。
xnx_reward v1.1.8 修复帖子页 500,升级后确认
post_list_inc_userinfo_actions_after.htm与thread_list_inc_subject_after.htm编译产物中 static 声明仅 1 处。已知接受项:同秒多次待审头像 glob 歧义(罕见)、st_import 暂存无 TTL(私有存储 0700)。



