innerHTML = userInput 必须禁用,因为浏览器会无条件解析执行其中的HTML标签和事件处理器,导致XSS攻击;必须使用白名单净化(如DOMPurify、bleach、sanitize-html),配合CSP兜底,且不同上下文需不同转义方式。

直接用 innerHTML 插入用户内容就是高危操作,浏览器会无条件解析执行其中的标签和事件处理器——哪怕只是一行 
,也能立刻窃取凭证。
为什么 innerHTML = userInput 必须禁用
这不是“谨慎点就好”的问题,而是语义层面失控:你把字符串交给 DOM 解析器时,它根本不管这串字符来自表单、URL 还是数据库,只按 HTML 规则解析。常见错误现象包括页面突然弹窗、控制台报 Failed to load resource 指向陌生域名、DOM 中莫名多出 或隐藏 。
- Vue 的
v-html、React 的 dangerouslySetInnerHTML 同样危险,不是加了前缀就安全,而是必须前置净化
-
document.write()、insertAdjacentHTML() 本质等同于 innerHTML,全部规避
- 正则删
<script></script> 是典型黑名单陷阱:ipt、)
、 全部逃逸
必须用白名单净化,而不是转义
转义(如 html.escape())只适用于纯文本上下文;一旦业务需要保留部分格式(比如富文本编辑器输出),就必须走白名单过滤——只允许明确列出的安全标签和属性,并校验 URL 协议。
- 前端推荐
DOMPurify.sanitize(htmlString):默认禁用 script、onerror、javascript: 等,无需额外配置就能防住 99% 的绕过
- Python 后端用
bleach.clean(html, tags=['p', 'br', 'strong'], attributes={'a': ['href'], 'img': ['src']}, protocols=['http', 'https', '/'])
- Node.js 用
sanitize-html,必须显式声明 allowedTags 和 allowedAttributes,并设置 allowedSchemesByTag 限制 a 和 img 的协议
- 服务端模板里写
{{ user_input|safe }}(Jinja2/Django)或 (EJS)等于主动开门——只有 100% 确认已用白名单净化后才能加 |safe
CSP 不是补丁,而是最后一道闸
CSP 不能替代净化,但能兜底:即使恶意代码被误插入,Content-Security-Policy 也能阻止其执行脚本、加载外部资源或触发内联事件。
- 必须设
script-src 'self' 并移除 'unsafe-inline' 和 'unsafe-eval'
- 对富文本场景需谨慎启用
script-src 'nonce-随机生成的一次性值',并在对应 <script></script> 标签中添加匹配的 nonce 属性
- 别依赖
:Chrome 120+、Firefox 125+ 已全面弃用,必须用 HTTP 响应头
- 上线前务必用 DevTools 的 Network → Headers 确认响应里真有该字段——引号、空格、分号、
always 参数错一个,整个防护就失效
真正容易被忽略的是:白名单净化库(如 DOMPurify)必须显式调用,不能只引入不使用;而服务端输出时,同一段用户输入在 HTML 标签体、属性值、JS 字符串、URL 中的转义规则完全不同——混用一种方式处理所有上下文,等于没防。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!