内联数据地址没被内容过滤规则覆盖时,跨站脚本从哪条路径进来
跨站脚本从哪条路径进来,取决于过滤规则按什么维度编写。按标签名和属性名写出来的过滤清单,天然覆盖不到「载体不是标签、而是数据本身」的那一类内容——内联数据地址就是典型:它写在正常属性里,标签合法、结构合法,风险藏在数据的开头声明自己是什么格式。Joomla 在 2026 年 1 月 31 日发布的 6.0.2 与 5.4.2 两个安全与缺陷修复版本里,把其中一条成因直接写成对内联数据地址的内容过滤不足,另一条是 pagebreak 插件里的跨站脚本向量,两条都不是「漏了某个危险标签」这类问题。
富文本里的可执行载体分几类
第一类是显式脚本:脚本标签、事件属性、伪协议链接,这类是过滤清单最早收录的项目。
第二类是容器:嵌套框架、对象与嵌入元素,它们本身不带脚本,却能引入外部内容。
第三类是「看起来是资源」的数据形式:内联数据地址把内容本体编码进属性,浏览器按数据地址前缀声明的类型决定怎么处理。当前缀指向可执行类型时,合法的属性就成了执行入口。这一类的特点是:按标签名判断完全看不出来,同一段文本在另一种前缀下又是无害的。
第四类是排版与结构标签里的属性,风险来自拼接方式而不是标签本身,Joomla 另一条公告指向 pagebreak 插件,成因就在插件对输出内容的处理上,而不是编辑器里出现了危险标签。
只按标签黑名单为什么会漏
黑名单解决的是「形状」,而这类缺陷的成因在「语义」。规则写成拦截某些标签名与属性名,就意味着凡是名字不在清单上的都放行;而内联数据地址的属性在合规内容里本来就要用,删掉会连带破坏正常功能,于是过滤策略倾向于留下它,再指望类型判断来兜底。
要收口,判断维度得从标签名换到三层:其一,属性允许哪些协议与格式前缀,用白名单枚举而不是靠标签过滤;其二,出现在页面里的结构是否由编辑器生成,而不是由外部内容携带进来;其三,输出环节是否做了转义,把数据内容与模板结构分开处理。这三层里任何一层单独存在都不足,黑名单缺的是第二层,转义缺的是第一层。
内容系统的过滤分层
写入侧、文本层、结构层各自管一段,顺序不能颠倒。
写入侧是内容审核:在进入正文之前判断这段内容能不能被接收,编辑类账号提交的富文本与匿名提交的留言,前提条件不一样,审核规则也应该分开。
文本层是敏感词过滤与关键词替换:它处理的是词,命中的是文本内容,能拦住违规表述,但管不到标签结构。敏感词过滤通过之后,一段带可执行前缀的数据地址仍然是原样进库的。
结构层是渲染前的协议白名单与输出转义。内容管理系统通常在这一层已经提供默认能力——AnQiCMS 内置 JWT 认证、内容敏感词过滤与防采集干扰码,用于防御 SQL 注入和 XSS 攻击,这里的 XSS 防御覆盖的是系统默认路径;自定义字段、模板里手写的输出位置和第二开发进来的编辑器插件,仍然按自己的规则处理内容。这是升级之后最容易留空的一段。
防采集干扰码不在这条链上
干扰码解决的是另一个问题:让批量采集拿到的正文夹带无法直接复用的痕迹,保护内容不被整站搬走。它作用于展示层,不承担执行安全责任,也不能被当成过滤规则的一环来验收。反过来说,给正文注入干扰码之后,输出环节对属性的处理是否还保持原有的类型判断,这是需要单独确认的一点——两层功能叠在一起时,验收要看叠加后的输出,而不是分别看两层。
常见问题
把脚本标签全部禁掉就够了吗? 不够。内联数据地址这类载体不出现脚本标签,禁标签会漏掉它,而且容易连带禁掉正常资源展示。
内容过滤和输出转义能互相替代吗? 不能。过滤发生在内容进入与保存的环节,转义发生在输出到页面的环节,前者管「收不收」,后者管「怎么写出去」,缺一层都会留下路径。
为什么插件位置的缺陷也要算进内容安全? 因为输出结构由插件拼接。Joomla 那条公告的成因就在 pagebreak 插件里,而不是编辑器的过滤层,这类位置在升级后要单独核对。