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

直接用 innerHTML 插入用户内容就是高危操作,浏览器会无条件解析执行其中的标签和事件处理器——哪怕只是一行 <img src="x" onerror='fetch("/steal?c="+document.cookie)' alt="HTML怎么防止动态内容被恶意脚本注入" >,也能立刻窃取凭证。
为什么 innerHTML = userInput 必须禁用
这不是“谨慎点就好”的问题,而是语义层面失控:你把字符串交给 DOM 解析器时,它根本不管这串字符来自表单、URL 还是数据库,只按 HTML 规则解析。常见错误现象包括页面突然弹窗、控制台报 Failed to load resource 指向陌生域名、DOM 中莫名多出 <iframe></iframe> 或隐藏 <div style="display:none">。
<ul>
<li>Vue 的 <code>v-html、React 的 dangerouslySetInnerHTML 同样危险,不是加了前缀就安全,而是必须前置净化
document.write()、insertAdjacentHTML() 本质等同于 innerHTML,全部规避<script></script> 是典型黑名单陷阱:<scr>ipt</scr>、<img src="javascript:alert(1)" alt="HTML怎么防止动态内容被恶意脚本注入" >、<div onclick="alert(1)"> 全部逃逸
<h3>必须用白名单净化,而不是转义</h3>
<p>转义(如 <code>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-<font color="green">随机生成的一次性值</font>',并在对应<script></script>标签中添加匹配的nonce属性 - 别依赖
<meta http-equiv="Content-Security-Policy">:Chrome 120+、Firefox 125+ 已全面弃用,必须用 HTTP 响应头 - 上线前务必用 DevTools 的 Network → Headers 确认响应里真有该字段——引号、空格、分号、
always参数错一个,整个防护就失效
真正容易被忽略的是:白名单净化库(如 DOMPurify)必须显式调用,不能只引入不使用;而服务端输出时,同一段用户输入在 HTML 标签体、属性值、JS 字符串、URL 中的转义规则完全不同——混用一种方式处理所有上下文,等于没防。



















