未发布文章下面的留言,匿名访客能不能读到
未发布文章下面的留言能不能被匿名访客读到,不看这篇文章有没有发布,而看读取留言的那个接口有没有单独判过一次可见性。常见的误解是「正文对外不可见,评论自然也不可见」——恰恰相反,正文和它挂载的评论、留言是两层各自独立的数据,父对象不可见并不自动让子对象不可见。一款主流程序在 2026 年 10 月的安全版本里专门修复了一条「私有与未发布文章的评论被未授权读取」的问题,就是这一层漏判的典型样本。
这类问题为什么出现在评论而不是正文
正文的可见性最容易做对:列表查询天然按状态过滤,草稿和待发布根本不会出现在对外列表里,直接访问未发布的正文地址也通常被拦在状态校验之外。麻烦在于评论是一套独立的记录,它们有各自的列表接口和详情接口。列表接口往往带着「只要已发布正文下的评论」这一层过滤,而详情接口常常只按评论自身的 ID 取数,不回查它所属的那篇文章此刻是什么状态。匿名访客只要拿到一个评论 ID,就可能绕过正文那道门。
父对象不可见时子对象该跟谁
原则只有一句:子对象不继承父对象的可见性,它要在被读取的那一刻重新查一次父对象的状态。落到判断顺序上,读一条留言应当先定位它属于哪篇文档,再看那篇文档是草稿、待发布、正式还是回收站,只有正文对外可见时,留言内容才随之返回。把这条顺序写反,就会出现「正文 403、留言 200」这种自相矛盾的结果。
只在校验列表漏掉校验详情会发生什么
列表和详情是两条代码路径,漏洞几乎总在被后写的那条上。
| 读取路径 | 是否回查正文状态 | 匿名可见 | 后果 |
|---|---|---|---|
| 评论列表(挂在已发布文章下) | 是 | 仅已发布 | 符合预期 |
| 单条评论详情(按 ID) | 常被漏 | 可能读到未发布正文下的留言 | 越权读取 |
| 待审核留言 | 是 + 审核态 | 否 | 需再过内容审核 |
| 草稿正文的预览 | 带预览参数 | 授权会话内 | 预览参数不传给留言接口 |
关键一行是第二行:详情接口只认评论 ID、不回查父文档状态时,未发布文章下的留言就成了公开的旁路。同一次修复里还包含评论管理页经待审评论触发的存储型 XSS,这提醒另一件事——留言不仅会被读到,还可能被写回管理界面执行,所以内容审核与敏感词过滤要在写入侧就生效。
站内的文档状态模型怎么表达可见性
安企CMS 把文档分成正式文档、草稿、待发布、回收站四种状态,草稿链接带预览参数可在授权会话里预览,删除正式文档是移入回收站而非物理删除。这套状态本身就是可见性判断的依据:任何读接口在返回内容前,都应把目标文档的当前状态作为前置条件,而不是假定调用方只会从对外列表点进来。
留言与评论接口该按哪三层收口
对照 API 调用面,留言和评论这类接口要过三道:第一道是父文档状态(是否正式、是否对外可见),第二道是子记录自身状态(是否已过审核、是否被标为垃圾),第三道是角色与权限(未登录能看已通过的,作者能看自己相关的,待审只给审核角色)。三层里最容易被省掉的是第二、三层对详情接口的复用。
自查三步
先造一篇待发布文档,在它下面留一条已通过审核的留言;再用未登录会话直接请求该留言的详情接口,看是空结果还是完整内容;最后核对同一接口对回收站、对未过审留言是否同样拒读。三步里只要第二步返回了内容,就是可见性判定漏在了详情这条路径上。
常见问题
问:把草稿正文地址藏起来不就行了吗? 答:不够。留言有独立的读取入口,即便没人知道正文地址,评论 ID 被枚举到就会暴露,收口要落在接口的状态校验上。
问:预览参数带上不就能看留言了? 答:预览参数服务的是正文预览,留言接口不认这个参数才是要检查的点——它认了反而扩大面,不认才符合「按文档状态判定」。
问:内容审核能挡住这类越权吗? 答:审核管的是留言内容能不能对外,越权管的是它属于的文章能不能对外,两件事要分别判,不能靠审核一道兜底。