最直接的CSS变量篡改入口是document.documentElement.style.setProperty,攻击者可单行JS破坏UI布局;CSP的style-src对此无效;需服务端可信注入+前端getComputedStyle轮询检测+calc()/clamp()硬编码替代动态赋值。

document.documentElement.style.setProperty 是最直接的篡改入口
攻击者只需一行 JS 就能让整个布局失效:document.documentElement.style.setProperty('--header-height', '0px')。这不是边缘场景,而是 DOM-based XSS 的常见落地方式。它不依赖服务端、不触发网络请求、不修改 HTML 源码,纯前端运行时就能破坏关键 UI(比如把风控提示栏高度设为 0、让支付按钮 display: none)。你写的 :root 变量声明本身没有任何防护能力——CSS 变量是运行时可读可写的,浏览器不会校验调用来源。
CSP 的 style-src 对 CSS 变量完全无效
很多人误以为加了 style-src 'self' 就能防住变量篡改,其实 CSP 只管资源加载(<style></style> 标签、@import、外部 CSS 文件),不管运行时 JS 对 CSSOM 的操作。即使你禁用了 unsafe-inline、启用了 nonce,setProperty 依然畅通无阻。真正起作用的是服务端控制 + 前端检测组合:服务端确保初始变量值可信(如 SSR 注入固定值),前端用 getComputedStyle 轮询关键渲染结果,而非监听 style 属性变化。
用 calc() 或 clamp() 硬编码替代 JS 动态赋值
对必须响应用户行为但又不能被篡改的布局参数(如侧边栏宽度、弹窗最大高度),放弃 JS 设置变量的方式,改用 CSS 原生函数硬编码逻辑:
-
--sidebar-width: calc(256px + var(--spacing) * 2);—— 把计算留在 CSS 内部,JS 不碰变量 -
max-width: clamp(320px, 80vw, 1200px);—— 直接用clamp()替代var(--max-width) - 关键变量只在
:root中声明,且不暴露 setter 接口;业务组件中禁止调用element.style.setProperty修改全局变量
getComputedStyle 检测比监听 style 属性更可靠
监听 el.getAttribute('style') 或 MutationObserver 观察 style 属性变化毫无意义——攻击者根本不用改这个属性,直接调 el.style.display = 'none' 或 document.documentElement.style.setProperty 就绕过了。真正要捕获的是最终渲染效果:
立即学习“前端免费学习笔记(深入)”;
- 对支付按钮等关键元素,定期检查
getComputedStyle(button).display !== 'block' - 对头部区域,检查
getComputedStyle(header).height是否低于阈值(如'48px') - 轮询间隔建议 ≥ 500ms,避免性能抖动;可用
ResizeObserver辅助触发,但判断依据必须是getComputedStyle
最容易被忽略的是:CSS 变量一旦被 JS 动态覆盖,后续所有依赖它的 var() 都会立刻走新值,中间没有缓存、没有回滚机制。防护不是靠“不让改”,而是“改了也能及时发现并恢复”。


















