直接使用 innerHTML 插入用户内容是高危操作,因浏览器会解析执行其中的标签和脚本;常见错误场景包括评论区渲染、搜索关键词高亮、富文本编辑器输出直插 DOM 等;应避免 innerHTML、document.write()、insertAdjacentHTML() 及 Vue 的 v-html、React 的 dangerouslySetInnerHTML,必须前置白名单净化。

直接用 innerHTML 插入用户内容就是高危操作
只要把不可信字符串赋给 innerHTML,浏览器就会解析执行其中的标签和脚本。哪怕只是 <img src="x" onerror="fetch('/steal?c='+document.cookie)"> 这种看似无害的 HTML,也会触发跨域请求窃取凭证。
常见错误场景包括:评论区渲染、搜索关键词高亮、富文本编辑器输出直插 DOM、API 返回的 HTML 字段未过滤就塞进页面。
- 永远不要写
el.innerHTML = userInput - 避免
document.write()、insertAdjacentHTML()等等同于 innerHTML 的 API - Vue 的
v-html、React 的dangerouslySetInnerHTML同样危险,必须前置净化
富文本必须用白名单 + 净化库,不能靠正则或黑名单
黑名单过滤(比如删掉 <script>)极易被绕过:<scr<script>ipt>>、<code><IMG SRC="javascript:alert(1)">、<div onclick=alert(1)> 都能逃逸。
正确做法是只允许明确列出的安全标签和属性,并校验 URL 协议。
立即学习“前端免费学习笔记(深入)”;
- 前端推荐用
DOMPurify.sanitize(htmlString),默认已禁用script、onerror、javascript:等 - 后端 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的协议
服务端模板里绕过转义(如 |safe)等于主动开门
很多开发者以为“模板引擎默认转义就安全了”,却在需要格式时随手加 |safe 或 {{ value|safe }},结果把未经净化的原始 HTML 直接输出。
关键判断点:只要变量来源含用户输入(表单、URL 参数、数据库读取),就必须走净化流程;|safe 只能在 100% 确认该值已通过白名单净化后使用。
- Jinja2 中
{{ user_input }}安全,{{ user_input|safe }}危险 - Django 模板同理,
{{ user_input }}自动转义,{{ user_input|safe }}绕过防护 - EJS 默认不转义,必须用
或启用escape选项
CSP 不是补丁,而是最后一道闸,不能替代编码层防护
即使配置了严格的 Content-Security-Policy,比如 script-src 'self',也不能阻止攻击者利用 <img src=x onerror=...> 或 <a href="javascript:..."> 实施数据泄露或 CSRF。
CSP 的作用是限制恶意代码执行后的危害范围,不是防止注入本身。它无法修复 innerHTML 漏洞,也不能阻止属性级 XSS。
- 必须配合服务端 HTML 实体编码(如 Python
html.escape())或前端textContent使用 - 对需动态插入 HTML 的场景,CSP 应搭配
nonce或hash机制,而非放行'unsafe-inline' - 开发阶段建议先用
Content-Security-Policy-Report-Only收集违规行为,再上线正式策略
href、src 或事件处理器中,这种地方既不会被模板引擎自动转义,也常被 CSP 漏判。处理时得单独校验协议、剥离危险前缀、拒绝 javascript: 和 data: 等非法 scheme。



















