页面缓存被当成脚本执行,问题出在参数编码还是缓存目录
页面缓存文件被当成脚本执行,不是单点问题。它是两段链路同时失守的结果:前一段让攻击内容能写进缓存,后一段让这份缓存被解释执行。只修参数编码或只修目录权限,都挡不住完整链路。
缓存键为什么会带外部输入
静态化缓存的常见做法是把请求路径映射成一个文件名,命中就直接把文件送出去。问题出现在映射时把外部参数原样拼进了键里。同行系统的一条修复记录把成因写得很具体:PbootCMS 在 V3.2.24(2026-09-01)的更新日志中写明,修复了前台分页参数编码与查询分隔符绕过导致 HTML 缓存投毒、缓存文件被当作脚本执行的问题。
这里有两类绕过。一类是编码差异:同一个值经过不同的百分号编码写法,在字符串比较时不相等,于是被当成两个不同页面,其中一个的缓存内容是可以控制的。另一类是分隔符:查询串的分隔字符没有做转义,拼进文件名后改变了路径归属,让写入落到意料之外的位置。
编码与分隔符的两类绕过
正确的处理顺序是先把参数规范化,再生成键。规范化意味着按同一套规则解码、拒绝非法字符,最后用一个稳定的散列值当文件名,而不是把用户输入直接当路径片段。这样即使输入形式多变,指向的仍是同一个键,也不可能出现「同键不同页」或者把分隔符当目录分隔符的情况。
「同键不同页」的麻烦不止于命中率:缓存内容与请求路径的对应关系一旦被打破,展示的就不再是那个路径本该有的内容,投毒才能持久留在页面上。
落盘位置与执行权限
第二段问题在文件层。缓存目录如果被 Web 服务器按脚本处理,任何写进该目录的文件都会获得执行能力。收口方式是把写入目录与可执行目录分离:缓存只作为数据被程序读取,不通过 URL 直接寻址;目录层面关闭脚本解析,必要时再单独设一条拒绝规则。
这一步和内容层的防护是两条线。站内的敏感词过滤与内容审核作用于入库文本,防的是文本层面的问题内容;接口鉴权与注入防护走的是请求处理链。文件层的执行问题三者都挡不住,必须在部署与目录权限这一层解决。
站内的四段收口
按链路排一遍会更清楚。第一段是路由与伪静态规则:改写后的路径决定缓存键的输入,规则改动后要重新核对键的生成方式,避免把改写参数直接拼进去。第二段是键的生成:规范化输入、拒绝非法字符、使用不可预测的文件名。第三段是写入:只写数据目录,校验目标路径仍在预期范围内。第四段是执行:缓存路径不接受脚本解析,且这条限制写在部署配置里而不是靠约定。
排查时也按这四段倒序看:先确认目录是否还能被执行,再确认写入路径是否可被外部控制,再确认键生成有没有规范化,最后回看规则改动记录。
常见问题
问:只清缓存能解决问题吗? 不能。清掉的是已有的投毒内容,生成与执行这两段条件还在,之后仍会重新出现。
问:改了权限为什么还能被触发? 如果缓存键仍可被外部控制,改写页面内容这条路径依然有效,只是不再直接执行而已。
问:静态化本身要不要停? 不必整体停。要改的是键的生成方式与落盘位置,让静态化不再依赖可预测的文件名和可执行的目录。