HTML Sanitizer API
HTML Sanitizer API 允许开发人员获取 HTML 字符串,并在将其插入到 DOM 或 Shadow DOM 时,过滤掉不需要的元素、属性和其他 HTML 实体。
概念与用法
Web 应用程序通常需要在客户端处理不受信任的 HTML,例如,作为客户端模板解决方案的一部分,渲染用户生成的内容,或者包含来自其他站点的数据到框架中。
注入不受信任的 HTML 会使站点容易受到各种类型的攻击。特别是,跨站脚本 (XSS) 攻击通过将不受信任的 HTML 注入到 DOM 中来工作,然后在当前源的上下文中执行 JavaScript,从而允许恶意代码像从站点源提供的那样运行。可以通过在将不安全的 HTML 元素和属性注入到 DOM 之前将其删除来缓解这些攻击。
HTML Sanitizer API 提供了许多方法,用于在将 HTML 输入注入到 DOM 之前,从 HTML 输入中删除不需要的 HTML 实体。这些方法分为 XSS 安全版本(强制删除所有不安全的元素和属性)和可能不安全的版本(开发人员可以完全控制允许的 HTML 实体)。
净化方法
HTML Sanitizer API 提供 XSS 安全和 XSS 不安全的方法,用于将 HTML 字符串注入到 Element 或 ShadowRoot 中,以及将 HTML 解析为 Document 中。
- 安全方法:
Element.setHTML()、ShadowRoot.setHTML()和Document.parseHTML()。 - 不安全方法:
Element.setHTMLUnsafe()、ShadowRoot.setHTMLUnsafe()和Document.parseHTMLUnsafe()。
所有方法都接受要注入的 HTML 和可选的净化器配置作为参数。该配置定义了在注入之前将从输入中过滤掉的 HTML 实体。Element 方法是上下文感知的,并且还会删除 HTML 规范不允许在目标元素中出现的任何元素。
安全方法始终删除 XSS 不安全的元素和属性。如果未将净化器作为参数传递,它们将使用默认净化器配置,该配置允许除已知不安全的元素和属性(例如 <script> 元素和 onclick 事件处理程序)之外的所有元素和属性。如果使用自定义净化器,它将隐式更新以删除任何非 XSS 安全的元素和属性(请注意,传递的净化器不会被修改,并且如果与不安全方法一起使用,仍可能允许不安全的实体)。
在注入不受信任的 HTML 内容时,应使用安全方法而不是 Element.innerHTML、Element.outerHTML 或 ShadowRoot.innerHTML。例如,在大多数情况下,您可以使用带有默认净化器的 Element.setHTML() 作为 Element.innerHTML 的直接替换。同样的方法也可以用于注入不需要包含任何 XSS 不安全元素的受信任 HTML 字符串。
XSS 不安全方法将使用作为参数传递的任何净化器配置。如果没有传递净化器,则将注入上下文允许的所有 HTML 元素和属性。这类似于使用 Element.innerHTML,但该方法将解析 shadow roots,删除不适合上下文的元素,并允许在使用属性时不允许的一些其他输入。
不安全方法只应用于包含某些 XSS 不安全元素或属性的不可信 HTML。这仍然不安全,但允许您通过将不安全实体限制在最小集合来降低风险。例如,如果您想注入不安全的 HTML,但由于某种原因您需要输入包含 onblur 处理程序,您可以通过修改默认净化器并使用不安全方法来更安全地实现,如下所示:
const sanitizer = Sanitizer(); // Default sanitizer
sanitizer.allowAttribute("onblur"); // Allow onblur
someElement.setHTMLUnsafe(untrustedString, { sanitizer });
净化器配置
净化器配置定义了当使用净化器时,哪些 HTML 实体将被允许、替换或移除,包括元素、属性、数据属性和注释。
有两个非常密切相关的净化器配置接口,其中任何一个都可以传递给所有净化方法。
SanitizerConfig是一个字典对象,它定义了在使用配置时允许或不允许的元素或属性数组,以及指示是否允许或省略注释和数据属性的属性等。Sanitizer本质上是SanitizerConfig的一个包装器,它提供了方法来符合人体工程学地、一致地从配置中的各种列表中添加和删除实体。例如,您可以使用一个方法添加一个允许的属性,它也会从不允许的数组中删除该属性(如果存在)。该接口还提供了返回底层SanitizerConfig副本以及更新净化器以使其 XSS 安全的方法。它可能会提供用于构建它的净化器配置的标准化,使其更容易理解和重用。
虽然您可以在任何净化方法中使用这两种接口,但 Sanitizer 可能比 SanitizerConfig 更有效地共享和重用。
允许和移除配置
您可以通过两种方式构建配置:允许配置和移除配置。
在“允许配置”中,您指定要*允许*的元素和属性(或替换为子元素):输入中的所有其他元素/属性都将被删除。例如,以下配置仅允许 <p> 和 <div> 元素,以及任何元素上的 cite 和 onclick 属性。它还会将 <b> 元素替换为其子节点,有效地剥离其嵌套内容的样式。
const sanitizer = Sanitizer({
elements: ["p", "div"],
replaceWithChildrenElements: ["b"],
attributes: ["cite", "onclick"],
});
当使用允许配置时,很容易理解当解析 HTML 时,哪些元素将在 DOM 中被允许。当您确切知道要在特定上下文中注入哪些 HTML 实体时,它们非常有用。
在“移除配置”中,您指定要移除的 HTML 元素和属性:净化器允许任何其他元素和属性(但如果您使用安全净化器方法,或者如果元素在上下文中不允许,则可能会被阻止)。例如,以下净化器将移除与前一个代码中允许的相同元素:
const sanitizer = Sanitizer({
removeElements: ["p", "div", "b"],
removeAttributes: ["cite", "onclick"],
});
当您想使用默认净化器设置,但可能限制一些额外的实体时,移除配置很有用。
不鼓励同时使用允许和移除配置,因为它会使配置更难理解且解析效率更低。请注意,同时包含允许和移除实体的配置总是可以简化为允许配置,其中原始移除列表中的任何实体都已删除。
如果您将包含允许和移除配置的 SanitizerConfig 传递给净化器方法,它们将抛出 TypeError。在使用 Sanitizer 时,可以创建同时包含允许和移除列表的配置。如果允许列表包含任何项目,则首先解析允许列表,然后解析移除列表。如果允许列表中有任何项目,则移除列表影响很小。
净化和可信类型
可信类型 API 提供了机制,确保输入在传递给可能执行该输入的 API 之前,先通过用户指定的转换函数。此转换函数最常用于净化输入,但它并非必须如此:此 API 的主要目的是让开发人员轻松审计净化代码,而不是定义如何或是否进行净化。
安全的 HTML 净化方法不需要与可信类型一起使用。因为它们总是在输入 HTML 注入之前过滤所有 XSS 不安全的实体,所以不需要净化输入字符串或审计这些方法。
然而,不安全的 HTML 净化方法可能会根据净化器注入不受信任的 HTML,因此将与可信类型一起使用。这些方法可以接受字符串或 TrustedType 作为输入。如果还提供了净化器,则首先运行转换函数,然后运行净化器。
请注意,在这种情况下,转换函数不必净化输入字符串(尽管它可以),因为您可以使用净化器 API 来实现。在这种情况下,可信类型提供的信息是关于可能不安全的字符串在哪里被注入,从而更容易找到它们并检查净化器是否配置得当。
第三方净化库
在 Sanitizer API 之前,开发人员通常使用第三方库(如 DOMPurify)来过滤输入字符串,这些库可能从可信类型中的转换函数调用。
在注入不受信任的 HTML 字符串时,不应再需要使用这些库。该 API 与浏览器集成,并且比外部解析器库更了解解析上下文和允许执行的代码。
接口
Sanitizer实验性-
一个可重用的净化器配置对象,它定义了在净化不受信任的 HTML 字符串时应允许/移除哪些元素和属性。它用于将 HTML 字符串插入到 DOM 或 Document 中的方法。
SanitizerConfig-
一个定义净化器配置的字典。它可以在与
Sanitizer相同的地方使用,但使用和重用效率可能较低。
其他接口的扩展
XSS 安全方法
Element.setHTML()-
将 HTML 字符串解析为节点子树,删除元素上下文中任何无效的元素。然后删除净化器配置不允许的任何元素和属性,以及任何被认为是 XSS 不安全的元素和属性(即使配置允许)。然后将该子树作为元素的子树插入到 DOM 中。
ShadowRoot.setHTML()-
将 HTML 字符串解析为节点子树。然后删除净化器配置不允许的任何元素和属性,以及任何被认为是 XSS 不安全的元素和属性(即使配置允许)。然后将该子树作为
ShadowRoot的子树插入。 Document.parseHTML()-
将 HTML 字符串解析为节点子树。然后删除净化器配置不允许的任何元素和属性,以及任何被认为是 XSS 不安全的元素和属性(即使配置允许)。然后将该子树设置为
Document的根。
XSS 不安全方法
Element.setHTMLUnsafe()-
将 HTML 字符串解析为节点子树,删除元素上下文中任何无效的元素。然后删除净化器不允许的任何元素和属性:如果未指定净化器,则允许所有元素。然后将该子树作为元素的子树插入到 DOM 中。
ShadowRoot.setHTMLUnsafe()-
将 HTML 字符串解析为节点子树。然后删除净化器不允许的任何元素和属性:如果未指定净化器,则允许所有元素。然后将该子树作为
ShadowRoot的子树插入。 Document.parseHTMLUnsafe()-
将 HTML 字符串解析为节点子树。然后删除净化器不允许的任何元素和属性:如果未指定净化器,则允许所有元素。然后将该子树设置为
Document的根。
示例
以下示例展示了如何使用 默认 净化器来使用净化器 API(截至撰写本文时,配置操作尚未支持)。
使用带有默认净化器的 Element.setHTML()
在大多数情况下,使用默认净化器调用 Element.setHTML() 可以作为 Element.innerHTML 的直接替换。以下代码演示了该方法如何用于在将 HTML 输入注入到 id 为 target 的元素之前对其进行净化。
const untrustedString = "abc <script>alert(1)<" + "/script> def"; // Untrusted HTML (perhaps from user input)
const someTargetElement = document.getElementById("target");
// someElement.innerHTML = untrustedString;
someElement.setHTML(untrustedString);
console.log(target.innerHTML); // abc def
默认净化器或 setHTML() 方法不允许使用 元素,因此 <script>alert() 被删除。
请注意,使用带有默认净化器的 Element.setHTMLUnsafe() 将净化相同的 HTML 实体。主要区别在于,如果您将此方法与 Trusted Types 一起使用,它可能仍然会被审计。
someElement.setHTMLUnsafe(untrustedString);
使用允许净化器配置
此代码展示了如何使用 Element.setHTMLUnsafe() 和一个只允许 <p>、<b> 和 <div> 元素的允许净化器。输入字符串中的所有其他元素都将被移除。
const sanitizer = new Sanitizer({ elements: ["p", "b", "div"] });
someElement.setHTMLUnsafe(untrustedString, { sanitizer });
请注意,在这种情况下,您通常应使用 setHTML()。只有在需要允许 XSS 不安全的元素或属性时,才应使用 Element.setHTMLUnsafe()。
规范
| 规范 |
|---|
| HTML Sanitizer API # sanitizer |
浏览器兼容性
加载中…