跨站脚本攻击(XSS)长期以来一直是 Web 安全领域最难根治的漏洞之一。在过去近十年中,CWE-79(XSS 漏洞)始终位列最严重的三大 Web 安全漏洞。尽管内容安全策略(CSP)等防御手段为 Web 安全提供了强有力的防线,但由于其要求网站进行显著的架构调整以及持续的安全审查,在实际部署中并未获得广泛的采纳。
HTML Sanitizer API 的诞生正是为了填补这一安全空白。它提供了一种由浏览器原生支持的标准化方法,在将不可信的 HTML 插入页面之前,自动移除其中所有危险元素和属性——即“清理”(sanitize)不可信内容。开发者只需将原有的 innerHTML 赋值替换为 setHTML(),即可在不显著改动现有代码的情况下获得内置的 XSS 防护能力。
本文将基于 2026 年的最新规范进展,深入解析 HTML Sanitizer API 的核心设计、使用方法与实践要点。
特性 | 说明 |
API 类型 | 浏览器原生 Web API |
核心功能 | 过滤和清理不可信 HTML,移除危险标签和属性和不安全协议 |
适用场景 | 用户评论区、富文本编辑器、第三方内容嵌入、客户端模板渲染等 |
当前状态 | 已实现标准化,逐步获得主流浏览器支持 |
2026 年 2 月,Firefox 148 成为首个正式发布标准化 Sanitizer API 的浏览器。这一里程碑标志着 Web 平台原生 XSS 防护能力向前迈出了关键一步,其他主流浏览器预计也将很快跟进。
一、为什么需要 Sanitizer API?
场景 | 风险 | Sanitizer 解决方案 |
用户评论区 | <script>恶意代码</script> | 自动移除 <script> 标签 |
富文本编辑器 | onclick="窃取Cookie()" | 移除危险事件属性 |
第三方内容嵌入 | iframe src="恶意链接" | 过滤不受信任的标签 |
SVG 图像上传 | <svg><script>alert(1)</script> | 清理 SVG 中的脚本 |
CSS 注入 | style="background: url(javascript:alert(1))" | 移除危险的 style 内容 |
链接劫持 | <a href="javascript:恶意代码"> | 将 javascript: 协议置为 about:blank |
二、核心接口与配置
2.1 Sanitizer 类
Sanitizer 接口是一个可复用的配置对象,用于定义在将 HTML 字符串插入 Element 或 ShadowRoot 时,哪些元素、属性和注释应当被允许或移除。
const sanitizer = new Sanitizer(options);
2.2 SanitizerConfig 配置详解
SanitizerConfig 字典定义了清理器的具体行为。配置项主要分为以下几类:
配置属性 | 类型 | 说明 |
elements | Array | 允许保留的元素列表,可同时为每个元素指定允许或移除的属性 |
removeElements | Array | 需要完全移除的元素列表 |
replaceWithChildrenElements | Array | 用其子元素替换元素 |
attributes | Array | 允许保留的属性列表 |
removeAttributes | Array | 需要移除的属性列表 |
allowComments | Boolean | 是否保留 HTML 注释 |
allowDataAttributes | Boolean | 是否保留 data-* 属性 |
2.3 基础配置示例
const sanitizer = new Sanitizer({ elements: ["div", "p", "span", "a", "img"], removeElements: ["script", "style", "iframe"], replaceWithChildrenElements: ["b", "i"], attributes: ["class", "id"], removeAttributes: ["onclick", "onload", "onerror"]});
2.4 命名空间支持
SanitizerConfig 还支持通过 namespace 字段区分 HTML、SVG 和 MathML 中的同名元素与属性,满足更复杂的清理需求。默认命名空间为 HTML 命名空间。
const config = { elements: [ "div", { name: "circle", namespace: "http://www.w3.org/2000/svg" } ]};
2.5 三种元素处理策略对比
策略 | 配置字段 | 处理方式 | 示例 |
允许保留 | elements | 元素及其允许的属性被保留 | 示例一 |
完全移除 | removeElements | 整个元素被删除 | 示例二 |
替换为子元素 | replaceWithChildrenElements | 父标签被移除,子元素保留 | 示例三 |
示例一:元素及其允许的属性被保留
示例二:整个元素被删除
<script>alert(1)</script> →(空)
示例三:父标签被移除,子元素保留
三、清理方法:安全方法 vs 不安全方法
HTML Sanitizer API 提供了两套方法:XSS 安全方法 和 XSS 不安全方法,分别适用于不同场景。
3.1 XSS 安全方法(推荐日常使用)
安全方法始终会移除所有 XSS 不安全的元素和属性,即使用户传入了自定义的 Sanitizer 配置也无法绕过这一底线。
方法 | 所属对象 | 说明 |
Element.setHTML() | Element | 替代 innerHTML 的安全方法 |
ShadowRoot.setHTML() | ShadowRoot | Shadow DOM 中的安全注入 |
Document.parseHTML() | Document(静态) | 将 HTML 字符串解析为安全的 Document |
核心用法:setHTML() 可以直接作为 innerHTML 的替代品使用:
const untrustedString = 'abc <script>alert(1)</script> def';const element = document.getElementById('target');
element.setHTML(untrustedString);console.log(element.innerHTML);
3.2 XSS 不安全方法(慎用)
当输入的 HTML 需要包含某些不安全的元素或属性时,可以使用不安全方法。这些方法会严格按照传入的 Sanitizer 配置执行清理,不会自动补加安全限制。
方法 | 所属对象 | 说明 |
Element.setHTMLUnsafe() | Element | 使用自定义配置注入,不自动移除不安全内容 |
ShadowRoot.setHTMLUnsafe() | ShadowRoot | Shadow DOM 中的不安全版本 |
Document.parseHTMLUnsafe() | Document(静态) | 解析 HTML 字符串为 Document,不自动移除不安全内容 |
const customConfig = { elements: ["div", "button"], attributes: ["onclick"] };element.setHTMLUnsafe(userInput, customConfig);
重要提醒:日常开发中应始终优先使用安全方法 setHTML())。不安全方法仅在“确实需要包含某些不安全实体、且已通过自定义配置将其限制到最小安全集合”的场景下使用。
四、Sanitizer 实例的动态方法
Sanitizer 实例除通过构造函数一次性配置外,还提供了一系列动态方法,允许在运行时逐步调整配置,而无需重新创建实例。
方法 | 说明 |
allowElement(elementName, attributes?) | 将元素加入允许列表 |
removeElement(elementName) | 将元素加入移除列表 |
replaceElementWithChildren(elementName) | 将元素替换为其子节点 |
allowAttribute(attributeName) | 将属性加入允许列表 |
removeAttribute(attributeName) | 将属性加入移除列表 |
setComments(allow) | 设置是否保留注释 |
setDataAttributes(allow) | 设置是否保留 data-* 属性 |
removeUnsafe() | 更新清理器配置,移除所有 XSS 不安全的 HTML 实体 |
get() | 获取当前配置的快照 |
const sanitizer = new Sanitizer();sanitizer.allowElement('div', ['class', 'id']);sanitizer.allowElement('img', ['src', 'alt']);sanitizer.removeAttribute('onclick');sanitizer.setComments(false);sanitizer.setDataAttributes(true);console.log(sanitizer.get());
五、清理配置的构建策略
策略一:允许列表(白名单)
只允许指定的元素和属性,其他一律移除。这种形式易于理解,尤其在确定目标上下文中应允许哪些 HTML 实体时非常有用。
const allowlistSanitizer = new Sanitizer({ elements: ["p", "strong", "em", "a", "img"], attributes: ["href", "src", "alt"]});
策略二:拒绝列表(黑名单)
允许大多数元素,只移除明确禁止的项。
const denylistSanitizer = new Sanitizer({ removeElements: ["script", "iframe", "object"], removeAttributes: ["onclick", "onerror", "onload"]});
策略三:基于默认配置调整
默认的 Sanitizer 配置已经是 XSS 安全的良好起点,它移除了所有 XSS 不安全的元素和属性(如 <script> 元素和 onclick 事件处理器等)。开发者可以在默认配置基础上,根据业务需求进行适度收紧或放松。
const defaultSanitizer = new Sanitizer();
const customSanitizer = new Sanitizer();customSanitizer.allowElement("button", ["onclick"]);element.setHTMLUnsafe(untrustedString, customSanitizer);
六、默认清理器配置详解
Sanitizer API 内置了一套默认清理器配置。该配置是 XSS 安全的良好起点,已知会移除以下类型的元素和属性:
始终移除的元素(即使传入自定义 Sanitizer 也仍会被安全方法移除):
始终移除的属性:
安全方法使用此默认配置时,XSS 不安全实体永远不会被注入到 DOM 中。开发者传入的自定义配置仅能在默认安全配置的基础上进一步收紧允许的实体范围,而不能放宽已禁止的不安全实体。
七、与 DOMPurify 的对比
HTML Sanitizer API 的设计目标并非完全取代 DOMPurify,而是为 Web 开发者提供一种原生、轻量、安全的备选方案。
特性 | HTML Sanitizer API | DOMPurify |
类型 | 浏览器原生 API,由 WICG 孵化和标准化 | 第三方 JavaScript 库 |
大小 | 0 KB(内置) | ~30 KB(需额外加载和解析) |
mXSS 防护 | 原生设计,只解析一次 HTML,从根本上杜绝 mXSS | 依赖两轮解析,存在潜在的 mXSS 风险 |
性能 | 原生实现,比 JavaScript 库更快 | JavaScript 实现 |
兼容性 | 现代浏览器 | 所有浏览器(含旧版) |
自定义性 | 中等,通过 SanitizerConfig 控制 | 高度可定制,提供丰富 Hooks |
更新维护 | 浏览器厂商负责 | 社区维护 |
SVG/MathML 支持 | 通过命名空间配置支持 | 内置支持 |
DOMPurify 在 Node.js 服务端环境中仍然扮演着重要角色。在 Sanitizer API 获得全面浏览器支持之前,DOMPurify 仍将是跨浏览器和跨环境(包括服务端 JS 需依赖 jsdom 时)最可靠的 HTML 清理方案。
在 Web 安全语境里,mXSS 指的是 Mutation-based XSS(基于突变的跨站脚本攻击),是一种比较隐蔽的 XSS 类型。
mXSS = 浏览器在解析/重构 HTML 时,把看似无害的内容“突变”成可执行的脚本。
八、完整应用示例
8.1 安全方法:替换 innerHTML
这是 Sanitizer API 最实用的场景——用 setHTML() 直接替换项目中的 innerHTML 赋值,以极低的代码改动换取更强的 XSS 防护。
// 包含恶意代码的用户输入const userComment = ` <div> <p>This is a great article!</p> <img src="x" onclick="alert('XSS')"> <script>stealCookies()</script> <a href="javascript:alert('evil')">Click me</a> </div>`;
document.getElementById('comment').setHTML(userComment);
<div><p>This is a great article!</p><img src="x"><a>Click me</a></div>
8.2 富文本编辑器场景
class SafeRichEditor { constructor(containerId) { this.container = document.getElementById(containerId); this.sanitizer = new Sanitizer({ elements: [ "p", "h1", "h2", "h3", "h4", "ul", "ol", "li", "blockquote", "strong", "em", "a", "img", "br", "hr" ], attributes: ["href", "target", "src", "alt"] }); }
setContent(html) { this.container.setHTML(html, { sanitizer: this.sanitizer }); }
getContent() { return this.container.innerHTML; }}
const editor = new SafeRichEditor('editor-container');editor.setContent(userInput);
8.3 在 Shadow DOM 中使用
const host = document.getElementById('host');const shadowRoot = host.attachShadow({ mode: 'open' });const userContent = '<p>Shadow DOM content</p><script>alert("XSS")</script>';
shadowRoot.setHTML(userContent);
// 结果:<p>Shadow DOM content</p>(script 标签被移除)
8.4 使用 replaceElementWithChildren() 清除样式
const sanitizer = new Sanitizer({ elements: ["p", "em", "strong"] });sanitizer.replaceElementWithChildren("strong");
const input = "<p>This is <strong>important</strong> text with <em>emphasis</em>.</p>";const element = document.getElementById("output");element.setHTML(input, { sanitizer: sanitizer });
// 输出:<p>This is important text with <em>emphasis</em>.</p>
8.6 将 HTML 字符串解析为安全的 Document
// 使用 parseHTML 静态方法解析 HTML 为安全的 Documentconst unsafeHtml = '<div>Hello</div><script>alert("XSS")</script>';const doc = Document.parseHTML(unsafeHtml);
const safeContent = doc.body.innerHTML;
九、与 Trusted Types 的配合使用
Trusted Types 和 Sanitizer API 是相辅相成的安全机制。Trusted Types 确保开发者不会忘记清理——任何试图将字符串注入 DOM API 的行为都会失败,除非传入一个 TrustedHTML 对象。Sanitizer API 则负责实际的清理工作,输出安全的 HTML 内容。
const policy = trustedTypes.createPolicy('sanitizer-policy', { createHTML: (input) => { const sanitizer = new Sanitizer(); const fragment = sanitizer.sanitize(input); const div = document.createElement('div'); div.appendChild(fragment); return div.innerHTML; }});
const safeHTML = policy.createHTML(userInput);element.innerHTML = safeHTML;
十、浏览器兼容性与生产环境建议
10.1 当前兼容性现状
截至 2026 年 5 月:
Firefox:Firefox 148+ 已正式支持 Sanitizer API,Firefox 成为首个发布标准化 Sanitizer API 的浏览器。
Chrome:Chrome 146+ 支持 setHTML(),Edge 同步 Chromium 版本。
Safari:尚未完全支持 Sanitizer API。
值得注意的是,截至 2026 年初,Sanitizer API 尚未达到 Baseline 状态(即在部分主流浏览器中尚不可用),因此生产环境中的代码应当进行特性检测并提供降级方案。
10.2 特性检测与降级方案
if (window.Sanitizer && Element.prototype.setHTML) { element.setHTML(userInput);} else { element.innerHTML = DOMPurify.sanitize(userInput);}
10.3 服务端清理
由于 Sanitizer API 是浏览器原生 API,在 Node.js 环境中可借助 jsdom 配合相关 polyfill(如 @ungap/sanitizer)实现服务端清理。服务端清理与客户端清理结合使用,可以构建纵深防御体系。
const { JSDOM } = require('jsdom');const { Sanitizer } = require('@ungap/sanitizer');const dom = new JSDOM('<!DOCTYPE html>');const sanitizer = new Sanitizer({ window: dom.window });const cleanHTML = sanitizer.sanitize(userInput);
十一、最佳实践
1. 优先使用 setHTML 替代 innerHTML
element.innerHTML = userInput;
element.setHTML(userInput);
2. 为不同场景配置不同的 Sanitizer
const commentSanitizer = new Sanitizer({ elements: ["p", "strong", "em", "a", "br"], attributes: ["href"]});
const blogSanitizer = new Sanitizer({ elements: ["p", "h1", "h2", "h3", "ul", "ol", "li", "img", "a", "blockquote", "code"], attributes: ["href", "src", "alt", "title"]});
3. 结合 Content Security Policy(CSP)
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'none'; object-src 'none'">
4. 生产环境使用特性检测
function safelyInjectHTML(element, html) { if (element.setHTML) { element.setHTML(html); } else { element.innerHTML = DOMPurify.sanitize(html); }}
5. 避免混淆配置策略
SanitizerConfig 的设计限制了同时指定“允许”和“移除”类型的配置项,以避免在清理策略上产生混淆。如果配置同时包含 elements(允许列表)和 removeElements(移除列表),将导致类型错误。开发者应根据使用场景选择一种策略:
十二、常见问题
Q1:为什么 setHTML() 不直接返回清理后的 HTML 字符串?
因为setHTML() 的核心设计在于将清理机制深度集成至浏览器的原生 HTML 解析器中,通过“解析 → 清理 → 注入”的原子化流程,从根本上杜绝了 mXSS 攻击的可能性。若将清理后的结果以字符串形式返回,将导致二次解析风险;因此,该方法选择直接操作 DOM,从而确保安全性。
Q2:安全方法与不安全方法的核心区别是什么?
Q3:默认配置足够安全吗?
对于大多数日常使用场景(如用户评论、富文本编辑等),默认配置已足够安全。但对于内容安全要求极高的场景(如金融、政府应用),建议传入自定义配置进一步限制允许的元素和属性范围,并结合 CSP 构建纵深防御。
Q4:如何实验和测试 Sanitizer API?
在将 Sanitizer API 引入实际网页之前,建议访问 Sanitizer API Playground (网址:https://sanitizer-api.dev/)进行实验和测试。
Q5:正则表达式清理的危险性
直接使用正则表达式匹配并剔除非法 HTML 标签存在严重的安全与可靠性风险。正则表达式难以正确解析复杂的 HTML 结构,容易被各种绕过技巧攻击。始终推荐使用专业的 HTML 清理方案(如 Sanitizer API 或 DOMPurify)。
总结
HTML Sanitizer API 是现代浏览器提供的一项原生安全工具,它通过以下优势为 Web 开发者带来了更便捷的 XSS 防护方案:
安全性高:由浏览器厂商维护,持续更新安全策略,默认配置即可防护常见 XSS 攻击。
性能优异:原生实现,相比 JavaScript 库更高效。
使用简单:API 设计简洁,setHTML() 可直接替换 innerHTML。
上下文感知:智能处理不同元素的安全需求,还支持 SVG 和 MathML 命名空间。
渐进增强:可与现有安全方案(CSP、Trusted Types、DOMPurify)协同工作。
实用建议:在支持 Sanitizer API 的浏览器中优先使用原生方案以获得最佳性能;对于需要兼容旧浏览器的项目,保留 DOMPurify 作为回退方案。同时,切勿完全依赖单一的防御手段——将 Sanitizer API 与 CSP 结合使用,可以为应用构建更加稳固的安全防线。
阅读原文:https://mp.weixin.qq.com/s/hWqdAA1VPHUaJk_NgkiPJg
该文章在 2026/5/22 11:09:37 编辑过