直接使用 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></script>)极易被绕过:<scr>ipt></scr>、<img src="javascript:alert(1)">、<div onclick="alert(1)"> 都能逃逸。
<p>正确做法是只允许明确列出的安全标签和属性,并校验 URL 协议。</p>
<ul>
<li>前端推荐用 <code>DOMPurify.sanitize(htmlString),默认已禁用 script、onerror、javascript: 等
bleach.clean(html, tags=['p', 'br', 'strong'], attributes={'a': ['href'], 'img': ['src']}, protocols=['http', 'https', '/'])
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:..."></a> 实施数据泄露或 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。











