只配标签白名单不够,因未限制属性值协议(如javascript:)、危险属性(如onerror)及style内联脚本,必须同步约束允许标签、属性及属性值协议三者,否则仍存在xss风险。

为什么只配标签白名单还不够?
很多人以为只要在 DOMPurify 或 js-xss 里写上 { whiteList: { p: [], strong: [], a: ['href'] } } 就算安全了。错。攻击者根本不用碰 <script></script>,靠一个 <a href="https://www.php.cn/link/e8644ee27d873e0bb207499b0279b8e8">点我</a> 就能触发 XSS —— 因为 href 属性本身不危险,但它的值协议没被限制。
白名单必须同时约束三件事:允许的标签、允许的属性、允许的属性值协议(如只允 http: / https: / mailto:)。漏掉任意一环,就等于开了后门。
- 只开
a标签 +href属性,但没禁javascript:→ 攻击成功 - 允许
img的src,但没校验是否以data:开头 → SVG 内嵌脚本可执行 - 允许
div的style属性,但没过滤expression()或url(javascript:...)→ 旧浏览器或特定渲染路径下仍可触发
DOMPurify 中 safeAttrValue 的实际用法
DOMPurify 默认对 href、src 等属性做基础协议校验,但如果你需要更细粒度控制(比如只允许站内链接、禁止外部图片),就得用 safeAttrValue 回调。
这个函数接收三个参数:tag(标签名)、attr(属性名)、value(原始值),返回处理后的安全值,或 null 表示丢弃该属性。
常见误操作是直接 return value.replace(...) —— 这样可能绕过内置校验。正确做法是先调用内置校验逻辑,再叠加业务规则:
const clean = DOMPurify.sanitize(dirtyHtml, {
ADD_TAGS: ['my-custom-tag'],
ADD_ATTR: ['data-id'],
FORBID_TAGS: ['script', 'object', 'embed'],
FORBID_ATTR: ['onerror', 'onclick', 'onload'],
SAFE_ATTR_VALUE: (tag, attr, value) => {
if (attr === 'href' && tag === 'a') {
try {
const url = new URL(value);
if (['http:', 'https:', 'mailto:'].includes(url.protocol)) {
return value;
}
} catch (_) {}
return null; // 非法协议,丢弃
}
if (attr === 'src' && tag === 'img') {
if (value.startsWith('https://cdn.example.com/')) {
return value;
}
return null;
}
return DOMPurify.defaults.SAFE_ATTR_VALUE(tag, attr, value); // fallback 到默认行为
}
});
js-xss 的 onTagAttr 怎么防止事件处理器漏杀
js-xss 的 onTagAttr 是拦截属性的关键钩子。它比简单配 whiteList 更可靠,因为能动态判断属性值是否含危险模式(比如 on\w+=、javascript:),而不是只看属性名是否在白名单里。
典型错误是只写 a: ['href'],结果漏掉 <a href="https://www.php.cn/link/93ac0c50dd620dc7b88e5fe05c70e15b" onclick="steal()">...</a> —— onclick 不在白名单里,但 js-xss 默认会保留未声明属性(取决于配置)。
- 必须显式设置
FORBID_ATTR: ['onerror', 'onclick', 'onload', 'onmouseover'],否则这些属性可能被透传 -
onTagAttr中对所有属性做正则匹配,比如/^on\w+$/i.test(attr)→ 直接 return null - 对
href值做二次校验时,别用.indexOf('javascript:') !== -1—— 攻击者可用ja&https://www.php.cn/link/93ac0c50dd620dc7b88e5fe05c70e15bx76;ascript:绕过,要用decodeURIComponent或 DOM 解析后再判
服务端二次校验为什么不能省?
前端调一次 DOMPurify.sanitize() 不代表数据安全。用户可以禁用 JS、绕过前端、或直接发请求到你的 API 接口。所有富文本内容进入数据库前,服务端必须独立执行一遍净化逻辑。
关键点不是“重复做一遍”,而是“用不同实现交叉验证”:前端用 DOMPurify,服务端用 bleach.clean()(Python)或 sanitize-html(Node.js),白名单配置完全一致。一旦两边输出不一致,说明某边存在解析差异或配置疏漏 —— 这正是攻击面所在。
容易被忽略的是:服务端净化后的内容,不能再用 innerHTML 插入页面。比如 Node.js 返回净化后字符串,前端拿到后直接 el.innerHTML = res.html,如果该字符串含 <img src="x" onerror="...">(未被彻底清除),依然会执行。应统一走 textContent + createRange().createContextualFragment() 安全插入,或交由框架(如 React 的 dangerouslySetInnerHTML)配合严格 CSP 处理。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











