必须使用 DOMPurify.sanitize() 净化用户 HTML 输入,因正则替换等手动方式易被绕过;直接 innerHTML/v-html 会执行恶意脚本、事件和危险协议,导致 XSS。

直接用 DOMPurify.sanitize() 处理用户输入的 HTML 字符串,是唯一可靠的做法;任何正则替换、字符串截断或手动白名单检查都容易被绕过,且维护成本高、漏洞多。
为什么不能跳过 DOMPurify 直接 innerHTML 或 v-html
浏览器会原样执行所有解析出的脚本、事件处理器和危险协议。哪怕只漏掉一个 onerror、javascript: 或 data: URI,就可能触发 XSS。
常见错误现象包括:
- 用户提交
,前端没净化,后端也没拦,cookie 就泄露了 - 富文本编辑器导出的 HTML 包含
,直接渲染导致弹窗 - 用
String.replace()去掉<script></script>,但攻击者写成<script></script>或就绕过了ipt>
前端使用 DOMPurify 的最小可行配置
不要依赖默认配置——它允许太多标签(如 style、iframe)和属性(如 class、id),而这些在多数业务场景里并不需要,反而增加攻击面。
推荐基础配置(适用于评论、简要富文本等场景):
import DOMPurify from 'dompurify';
const config = {
ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'a', 'ul', 'ol', 'li', 'code'],
ALLOWED_ATTR: ['href', 'title', 'target'],
FORBID_TAGS: ['script', 'style', 'iframe', 'object', 'embed'],
FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover'],
// 自动清理 javascript:、data: 等不安全协议
ALLOWED_URI_REGEXP: /^(https?|ftp):/i,
};
const cleanHtml = DOMPurify.sanitize(dirtyHtml, config);
注意点:
-
ALLOWED_URI_REGEXP必须显式设置,否则不会被拦截 -
target="_blank"要配合rel="noopener"才安全,但 DOMPurify 默认不加;如需保留,得用 hook 注入:DOMPurify.addHook("afterSanitizeAttributes", node => { if (node.tagName === "A" && node.hasAttribute("target")) node.setAttribute("rel", "noopener"); }) - 如果项目用了 Tailwind 或 Bootstrap,
class属性通常没必要放行,除非你明确控制了 class 白名单
Vue / React 中动态渲染前必须净化
框架的 v-html 或 dangerouslySetInnerHTML 不做任何过滤,它们只是把字符串丢给 innerHTML。这等于把钥匙交给用户。
正确做法是:
- Vue 2/3:始终用计算属性或
watch+DOMPurify.sanitize()处理原始值,再传给v-html - React:封装一个
SafeHtml组件,内部调用DOMPurify.sanitize(),禁止直接用dangerouslySetInnerHTML - 不要在模板里写
v-html="DOMPurify.sanitize(raw)"—— 模板中无法 import,且破坏响应式依赖追踪
示例(Vue 3 setup):
import { ref, computed } from 'vue';
import DOMPurify from 'dompurify';
const rawHtml = ref('<p>Hello</p>@@##@@');
const sanitizedHtml = computed(() =>
DOMPurify.sanitize(rawHtml.value, { ALLOWED_TAGS: ['p'] })
);
后端净化不是可选项,而是强制项
前端净化纯属“用户体验优化”——它能让用户立刻看到格式效果,但攻击者完全可以禁用 JS、用 curl 或 Postman 绕过,直连 API 提交恶意 HTML。
Node.js 后端典型用法(需搭配 jsdom):
const { JSDOM } = require('jsdom');
const createDOMPurify = require('dompurify');
const dom = new JSDOM('', { runScripts: 'dangerously', resources: 'usable' });
const DOMPurify = createDOMPurify(dom.window);
// 存库前必须净化
const clean = DOMPurify.sanitize(userInput, {
ALLOWED_TAGS: ['p', 'a'],
ALLOWED_ATTR: ['href']
});
关键提醒:
- 别信“我们只有内部管理员用编辑器”——只要内容会展示给其他用户,就必须净化
- 数据库里存的必须是净化后的 HTML,而不是原始输入;否则一旦某天换渲染方式(比如 SSR、邮件模板),就全崩了
- 如果用的是 Express,建议在中间件层统一处理
req.body.content,避免每个路由重复写
最常被忽略的一点:DOMPurify 的配置必须前后端一致。前端放开 iframe,后端却禁止,会导致渲染错乱;反之,前端限制严而后端宽松,就等于开了后门。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











