1. 引言
本节为非规范性内容。
Web 应用经常需要在客户端处理 HTML 字符串,
可能作为客户端模板解决方案的一部分,也可能作为渲染用户生成内容的一部分等。要以安全方式做到这一点很困难。
将字符串简单拼接后塞入
Element
的
innerHTML
的朴素做法充满风险,因为它可能以多种出人意料的方式触发 JavaScript 执行。
诸如 [DOMPURIFY] 之类的库尝试 通过在插入前仔细解析并净化字符串、构建 DOM 并通过允许列表过滤其成员来管理这一问题。 但事实证明这是一种脆弱的方法,因为暴露给 Web 的解析 API 并不总能以合理方式映射到浏览器在“真实” DOM 中实际渲染字符串时的行为。 此外,这些库需要随时间跟进浏览器行为的变化;曾经安全的做法可能会因新的平台级特性而变成定时炸弹。
浏览器相当清楚自己何时会执行代码。我们可以通过教会浏览器如何以安全方式从任意字符串渲染 HTML 来改进用户空间库, 并以更有可能随着浏览器自身解析器实现变化而维护与更新的方式来实现。本文档概述了一个旨在完成此目标的 API。
1.1. 目标
-
通过为开发者提供处理用户控制的 HTML 的机制,减轻基于 DOM 的跨站脚本(XSS)攻击风险, 防止在注入时直接执行脚本。
-
使 HTML 输出在当前用户代理内使用是安全的,并考虑其对 HTML 的当前理解。
-
允许开发者覆盖默认的元素和属性集合。添加某些元素和属性可以防止 script gadget 攻击。
1.2. API 概览
Sanitizer API 提供了将包含 HTML 的字符串解析为 DOM 树的功能,并按用户提供的配置过滤生成的树。方法以两种风格成对提供:
-
安全与 不安全: “安全”方法不会生成任何会执行脚本的标记。也就是说,它们应当避免 XSS。“不安全”方法会按指示解析和过滤输入。 另见:§ 4 安全注意事项。
-
上下文:方法定义在
Element和ShadowRoot上,会替换这些Node的子节点,并在很大程度上类似于innerHTML。 还在Document上提供静态方法,用于解析整个文档,与DOMParser的parseFromString()大体类似。
2. 框架
2.1. 清理器 API
Element
接口定义了两个方法,setHTML()
和
setHTMLUnsafe()。
二者均接收包含 HTML 标记的 DOMString
与可选配置。
partial interface Element { [CEReactions ]undefined setHTMLUnsafe ((TrustedHTML or DOMString ),html optional SetHTMLUnsafeOptions = {}); [options CEReactions ]undefined setHTML (DOMString ,html optional SetHTMLOptions = {}); };options
Element
的
setHTMLUnsafe(html, options)
方法步骤为:
-
令 compliantHTML 为调用 Get Trusted Type compliant string 算法,传入
TrustedHTML、 this 的 relevant global object、html、"Element setHTMLUnsafe" 与 "script" 的结果。 -
若 this 为
template元素,则令 target 为其 template contents;否则为 this 本身。 -
设置并 过滤 HTML,给定 target、this、 compliantHTML、options 和 false。
Element
的
setHTML(html, options) 方法步骤为:
-
若 this 为
template, 则令 target 为其 template contents;否则为 this。 -
设置并 过滤 HTML,给定 target、this、html、 options 和 true。
partial interface ShadowRoot { [CEReactions ]undefined setHTMLUnsafe ((TrustedHTML or DOMString ),html optional SetHTMLUnsafeOptions = {}); [options CEReactions ]undefined setHTML (DOMString ,html optional SetHTMLOptions = {}); };options
这些方法也在 ShadowRoot
上提供:
ShadowRoot
的
setHTMLUnsafe(html, options)
方法步骤为:
-
令 compliantHTML 为调用 Get Trusted Type compliant string 算法,传入
TrustedHTML、 this 的 relevant global object、html、 "ShadowRoot setHTMLUnsafe" 与 "script" 的结果。 -
设置并 过滤 HTML,使用 this、 this 的 shadow host(作为上下文元素)、 compliantHTML、options 和 false。
ShadowRoot
的
setHTML(html, options) 方法步骤为:
-
设置并 过滤 HTML,使用 this(作为目标)、this(作为上下文 元素)、 html、options 和 true。
Document
接口新增两个用于解析整个 Document
的方法:
partial interface Document {static Document parseHTMLUnsafe ((TrustedHTML or DOMString ),html optional SetHTMLUnsafeOptions = {});options static Document parseHTML (DOMString ,html optional SetHTMLOptions = {}); };options
parseHTMLUnsafe(html, options)
方法步骤为:
-
令 compliantHTML 为调用 Get Trusted Type compliant string 算法,传入
TrustedHTML、 current global object、html、"Document parseHTMLUnsafe" 与 "script" 的结果。 -
令 document 为一个新的
Document, 其 content type 为 "text/html"。注意:由于 document 没有浏览上下文,脚本被禁用。
-
将 document 的 allow declarative shadow roots 设为 true。
-
给定 document 与 compliantHTML,从字符串解析 HTML。
-
令 sanitizer 为使用 options 和 false 调用 根据选项获取一个 sanitizer 实例 的结果。
-
在 document 上以 sanitizer 和 false 调用 sanitize。
-
返回 document。
parseHTML(html, options) 方法步骤为:
-
令 document 为一个新的
Document, 其 content type 为 "text/html"。注意:由于 document 没有浏览上下文,脚本被禁用。
-
将 document 的 allow declarative shadow roots 设为 true。
-
给定 document 与 html,从字符串解析 HTML。
-
令 sanitizer 为使用 options 和 true 调用 根据选项获取一个 sanitizer 实例 的结果。
-
在 document 上以 sanitizer 和 true 调用 sanitize。
-
返回 document。
2.2. SetHTML 选项与配置对象。
一系列类似 setHTML()
的
方法都接收一个选项字典。当前仅定义了该字典的一个成员:
enum {SanitizerPresets };"default" dictionary { (SetHTMLOptions Sanitizer or SanitizerConfig or SanitizerPresets )= "default"; };sanitizer dictionary { (SetHTMLUnsafeOptions Sanitizer or SanitizerConfig or SanitizerPresets )= {}; };sanitizer
Sanitizer
配置对象封装了过滤配置。同一份配置可用于 “安全”
与“不安全” 方法,其中“安全”方法会对传入配置隐式执行
removeUnsafe
操作,并在未传入配置时使用默认配置。默认配置在“安全”与“不安全”方法间不同:“安全”方法以默认安全为目标,使用更严格的默认;而“不安全”方法默认不受限制。配置的使用意图是:在页面生命周期早期构建一份(或少数几份)配置,此后按需复用。这允许实现对配置进行预处理。
配置对象可被查询以返回配置字典,也可直接修改。
[Exposed =Window ]interface {Sanitizer constructor (optional (SanitizerConfig or SanitizerPresets )= "default"); // Query configuration:configuration SanitizerConfig get (); // Modify a Sanitizer’s lists and fields:boolean allowElement (SanitizerElementWithAttributes );element boolean removeElement (SanitizerElement );element boolean replaceElementWithChildren (SanitizerElement );element boolean allowAttribute (SanitizerAttribute );attribute boolean removeAttribute (SanitizerAttribute );attribute boolean setComments (boolean );allow boolean setDataAttributes (boolean ); // Remove markup that executes script.allow boolean removeUnsafe (); };
Sanitizer
具有一个关联的 SanitizerConfig
configuration。
constructor(configuration)
方法步骤为:
get() 方法的步骤为:
-
令 config 为 this 的 configuration。
-
-
对于任意 element 属于 config["
elements"]:-
如果 element["
attributes"] 存在:-
将 element["
attributes"] 设为对 element["attributes"] 按升序排序 的结果, 其中比较准则为 attrA 小于项 attrB。
-
-
如果 element["
removeAttributes"] 存在:-
将 element["
removeAttributes"] 设为对 element["removeAttributes"] 按升序排序 的结果, 其中比较准则为 attrA 小于项 attrB。
-
-
-
将 config["
elements"] 设为对 按升序排序 config["elements"] 的结果, 其中比较准则为 elementA 小于项 elementB。
-
-
否则:
-
将 config["
removeElements"] 设为对 按升序排序 config["removeElements"] 的结果, 其中比较准则为 elementA 小于项 elementB。
-
-
如果 config["
replaceWithChildrenElements"] 存在:-
将 config["
replaceWithChildrenElements"] 设为对 按升序排序 config["replaceWithChildrenElements"] 的结果, 其中比较准则为 elementA 小于项 elementB。
-
-
如果 config["
attributes"] 存在:-
将 config["
attributes"] 设为对 按升序排序 config["attributes"] 的结果, 其中比较准则为 attrA 小于项 attrB。
-
-
否则:
-
将 config["
removeAttributes"] 设为对 按升序排序 config["removeAttributes"] 的结果, 其中比较准则为 attrA 小于项 attrB。
-
-
返回 config。
allowElement(element) 方法的步骤为:
-
我们是拥有全局的允许列表还是全局的移除列表,以及
-
这些列表是否已经包含 element。
-
令 configuration 为 this 的 configuration。
-
将 element 设为以 规范化带属性的 sanitizer 元素 处理 element 的结果。
-
如果 configuration["
elements"] 存在:-
将 modified 设为从 configuration["
replaceWithChildrenElements"] 中 移除 element 的结果。 -
注释:我们需要确保按元素的属性不会与全局属性重叠。
-
如果 configuration["
attributes"] 存在:-
如果 element["
attributes"] 存在:-
将 element["
attributes"] 设为 从 element["attributes"] 中 去重 的结果。 -
将 element["
attributes"] 设为 差集: element["attributes"] 与 configuration["attributes"]。 -
如果 configuration["
dataAttributes"] 为 true:-
移除 所有 item,这些 item 属于 element["
attributes"], 且 item 为 自定义数据属性。
-
-
-
如果 element["
removeAttributes"] 存在:-
将 element["
removeAttributes"] 设为 从 element["removeAttributes"] 中 去重 的结果。 -
将 element["
removeAttributes"] 设为 交集: element["removeAttributes"] 与 configuration["attributes"]。
-
-
-
否则:
-
如果 element["
attributes"] 存在:-
将 element["
attributes"] 设为 从 element["attributes"] 中 去重 的结果。 -
将 element["
attributes"] 设为 差集: element["attributes"] 与 element["removeAttributes"] 带默认值 « »。 -
移除 element["
removeAttributes"]。 -
将 element["
attributes"] 设为 差集: element["attributes"] 与 configuration["removeAttributes"]。
-
-
如果 element["
removeAttributes"] 存在:-
将 element["
removeAttributes"] 设为 从 element["removeAttributes"] 中 去重 的结果。 -
将 element["
removeAttributes"] 设为 差集: element["removeAttributes"] 与 configuration["removeAttributes"]。
-
-
-
注释:这是拥有全局允许列表且已包含 element 的情况。
-
令 current element 为 configuration["
elements"] 中的 item,其满足 item[name] 等于 element[name] 且 item[namespace] 等于 element[namespace]。 -
如果 element 等于 current element,则返回 modified。
-
返回 true。
-
-
否则:
-
如果 element["
attributes"] 存在 或 element["removeAttributes"] 带默认值 « » 非 空:-
用户代理可以 向控制台报告一个警告,指出该操作不受支持。
-
返回 false。
-
-
将 modified 设为从 configuration["
replaceWithChildrenElements"] 中 移除 element 的结果。 -
如果 configuration["
removeElements"] 不 包含 element:-
注释:这是拥有全局移除列表但不包含 element 的情况。
-
返回 modified。
-
-
注释:这是拥有全局移除列表且包含 element 的情况。
-
从 configuration["
removeElements"] 中移除 element。 -
返回 true。
-
replaceElementWithChildren(element)
方法的步骤为:
-
令 configuration 为 this 的 configuration。
-
将 element 设为以 规范化 sanitizer 元素 处理 element 的结果。
-
如果 configuration["
replaceWithChildrenElements"] 包含 element:-
返回 false。
-
-
从 configuration["
removeElements"] 中移除 element。 -
添加 element 至 configuration["
replaceWithChildrenElements"]。 -
返回 true。
allowAttribute(attribute) 方法的步骤为:
-
令 configuration 为 this 的 configuration。
-
将 attribute 设为以 规范化 sanitizer 属性 处理 attribute 的结果。
-
如果 configuration["
attributes"] 存在:-
注释:如果我们有全局允许列表,需要添加 attribute。
-
如果 configuration["
dataAttributes"] 为 true 且 attribute 是 自定义数据属性,则返回 false。 -
如果 configuration["
attributes"] 包含 attribute, 则返回 false。 -
注释:修正按元素的允许和移除列表。
-
如果 configuration["
elements"] 存在:-
对每个 element 属于 configuration["
elements"]:-
如果 element["
attributes"] 带默认值 « » 包含 attribute:-
从 element["
attributes"] 中移除 attribute。
-
-
断言:element["
removeAttributes"] 带默认值 « » 不 包含 attribute。
-
-
-
追加 attribute 至 configuration["
attributes"] -
返回 true。
-
-
否则:
-
注释:如果我们有全局移除列表,需要移除 attribute。
-
如果 configuration["
removeAttributes"] 不 包含 attribute:-
返回 false。
-
-
从 configuration["
removeAttributes"] 中移除 attribute。 -
返回 true。
-
setComments(allow) 方法的步骤为:
setDataAttributes(allow) 方法的步骤为:
-
令 configuration 为 this 的 configuration。
-
如果 configuration["
attributes"] 不 存在,则返回 false。 -
如果 configuration["
dataAttributes"] 等于 allow,则返回 false。 -
如果 allow 为 true:
-
移除 任意 attr,这些 attr 属于 configuration["
attributes"], 且 attr 为 自定义数据属性。 -
如果 configuration["
elements"] 存在:-
对每个 element 属于 configuration["
elements"]:-
如果 element[
attributes] 存在:-
移除 任意 attr,这些 attr 属于 element[
attributes], 且 attr 为 自定义数据属性。
-
-
-
-
-
将 configuration["
dataAttributes"] 设为 allow。 -
返回 true。
2.3. 配置字典
dictionary {SanitizerElementNamespace required DOMString ;name DOMString ?= "http://www.w3.org/1999/xhtml"; }; // Used by "elements"_namespace dictionary :SanitizerElementNamespaceWithAttributes SanitizerElementNamespace {sequence <SanitizerAttribute >;attributes sequence <SanitizerAttribute >; };removeAttributes typedef (DOMString or SanitizerElementNamespace );SanitizerElement typedef (DOMString or SanitizerElementNamespaceWithAttributes );SanitizerElementWithAttributes dictionary {SanitizerAttributeNamespace required DOMString ;name DOMString ?=_namespace null ; };typedef (DOMString or SanitizerAttributeNamespace );SanitizerAttribute dictionary {SanitizerConfig sequence <SanitizerElementWithAttributes >;elements sequence <SanitizerElement >;removeElements sequence <SanitizerElement >;replaceWithChildrenElements sequence <SanitizerAttribute >;attributes sequence <SanitizerAttribute >;removeAttributes boolean ;comments boolean ; };dataAttributes
2.4. 配置不变式
配置可以且应该由开发者根据其用途进行修改。可选方式包括从零开始编写新的配置字典,通过修改方法修改已有Sanitizer的配置,或用get()得到已有Sanitizer的配置字典,修改后再创建新Sanitizer实例。
空配置(当用“unsafe”方法如setHTMLUnsafe调用时)允许所有内容。
配置"default"包含内置安全默认配置。
注意“safe”和“unsafe”净化方法有不同的默认值。
并非所有配置字典都是有效的。有效配置应避免冗余(如重复允许同一元素)和矛盾(如同时移除和允许同一元素)。
配置要有效,需满足如下条件:
-
混用全局允许列表与移除列表:
-
elements或removeElements可以存在, 但不能同时存在。 如果两者都缺失,则等价于将removeElements设置为 « »。 -
attributes或removeAttributes可以存在, 但不能同时存在。 如果两者都缺失,则等价于将removeAttributes设置为 « »。 -
dataAttributes在概念上是attributes允许列表的扩展。仅当使用了attributes列表时,才允许使用dataAttributes属性。
-
-
不同全局列表之间的重复条目:
-
在
elements、removeElements或replaceWithChildrenElements之间不得有重复条目(即不得有相同元素)。 -
在
attributes与removeAttributes之间不得有重复条目(即不得有相同属性)。
-
-
在同一元素上混用局部允许/移除列表:
-
当存在
attributes列表时,在同一元素上允许attributes与removeAttributes列表:两者都可存在、仅其一存在或都不存在。 -
当存在
removeAttributes列表时,在同一元素上允许attributes与removeAttributes列表中的任意一个或都不存在, 但不能同时存在二者。
-
-
同一元素上的重复条目:
-
在同一元素上,
attributes与removeAttributes之间不得有重复条目。
-
elements
元素允许列表还可以为给定元素指定允许或移除的属性。这意在映射到 [HTML] 的结构,其同时包含
全局属性 和适用于特定元素的局部 属性。全局与局部属性可以混用,但请注意,如果某个配置存在歧义——某属性在一处被允许而在另一处被禁止——通常视为无效。
全局 attributes
| 全局 removeAttributes
| |
|---|---|---|
局部 attributes
| 若属性在任一列表中匹配,则允许该属性。不得有重复条目。 | 只有当属性在局部允许列表中时才允许。 全局移除与局部允许列表之间不得有重复条目。 注意:全局移除列表对这个特定元素不起作用,但可能适用于没有局部允许列表的其他元素。 |
局部 removeAttributes
| 若属性在全局允许列表中且不在局部移除列表中,则允许该属性。局部移除必须是全局允许列表的子集。 | 若属性既不在全局移除列表也不在局部移除列表中,则允许该属性。 全局移除与局部移除列表之间不得有重复条目。 |
请注意其中的不对称性:大多数情况下,全局与逐元素列表之间不允许有重复;但当存在全局允许列表与逐元素移除列表时,后者必须是前者的子集。上表中仅聚焦重复条目的摘录如下:
全局 attributes
| 全局 removeAttributes
| |
|---|---|---|
局部 attributes
| 不允许有重复条目。 | 不允许有重复条目。 |
局部 removeAttributes
| 局部移除必须是全局允许列表的子集。 | 不允许有重复条目。 |
dataAttributes
设置允许 自定义数据属性。若将
dataAttributes
视为一个允许列表,则上述规则可直接延伸到 自定义数据属性:
全局 attributes
与 dataAttributes
已设置
| |
|---|---|
局部 attributes
| 所有 自定义数据属性 都被允许。任何 自定义数据属性 都不得被列入任何允许列表,否则将造成重复条目。 |
局部 removeAttributes
| 除非被列入局部移除列表,否则 自定义数据属性 是被允许的。 任何 自定义数据属性 都不得被列入全局允许列表,否则将造成重复条目。 |
用文字表述这些规则:
-
全局与局部列表之间的重复与交互:
-
如果存在全局
attributes允许列表,则所有元素的局部列表:-
若存在局部
attributes允许列表, 则这些列表之间不得有重复条目。 -
若存在局部
removeAttributes移除列表, 则其所有条目也必须出现在全局attributes允许列表中。 -
如果
dataAttributes为 true,则任何 自定义数据属性 都不得被列入任一允许列表。
-
-
如果存在全局
removeAttributes移除列表,则:-
若存在局部
attributes允许列表, 则这些列表之间不得有重复条目。 -
若存在局部
removeAttributes移除列表, 则这些列表之间不得有重复条目。 -
不能同时存在局部
attributes允许列表 与局部removeAttributes移除列表。 -
dataAttributes必须不存在。
-
-
SanitizerConfig
config 在以下所有条件均满足时,被认为是有效:
-
config 具有
elements或removeElements键之一,但不能同时存在两者。 -
config 具有
attributes或removeAttributes的键之一,但不能同时存在两者。 -
断言:config 中所有
SanitizerElementNamespaceWithAttributes、SanitizerElementNamespace与SanitizerAttributeNamespace条目均为规范化的,即分别经过 规范化 sanitizer 元素 或 规范化 sanitizer 属性 的处理(按需)。 -
config[
elements]、 config[removeElements]、 config[replaceWithChildrenElements]、 config[attributes] 或 config[removeAttributes] 中,若其存在, 则均不得存在重复项。 -
如果同时存在 config[
elements] 与 config[replaceWithChildrenElements], 则 config[elements] 与 config[replaceWithChildrenElements] 的交集为空。 -
如果同时存在 config[
removeElements] 与 config[replaceWithChildrenElements], 则 config[removeElements] 与 config[replaceWithChildrenElements] 的交集为空。 -
如果 config[
attributes] 存在:-
-
对于每个 config[
elements] 中的 element:-
element[
attributes] 与 element[removeAttributes] (若存在)均不得存在重复项。 -
config[
attributes] 与 element[attributes] (带默认值) « » 的交集为空。 -
element[
removeAttributes] (带默认值) « » 是 config[attributes] 的子集。 -
如果
dataAttributes存在且dataAttributes为 true:-
element[
attributes] 不得包含 自定义数据属性。
-
-
-
-
如果
dataAttributes为 true:-
config[
attributes] 不得包含 自定义数据属性。
-
-
-
如果 config[
removeAttributes] 存在:-
如果 config[
elements] 存在, 则对于每个 config[elements] 中的 element:-
element[
attributes] 与 element[removeAttributes] 不得同时存在。 -
element[
attributes] 与 element[removeAttributes] (若存在)均不得存在重复项。 -
config[
removeAttributes] 与 element[attributes] (带默认值) « » 的交集为空。 -
config[
removeAttributes] 与 element[removeAttributes] (带默认值) « » 的交集为空。
-
-
config[
dataAttributes] 不得存在。
-
注意:从字典设置配置将进行一定的规范化处理。特别地,如果允许列表与移除列表都缺失,则将其解释为一个空的移除列表。所以
{} 本身不是一个有效的配置,但它会被规范化为
{removeElements:[],removeAttributes:[]},这是有效的。选择此规范化步骤是为了让缺失的字典与空字典保持一致,即让
setHTMLUnsafe(txt) 与 setHTMLUnsafe(txt, {sanitizer: {}}) 的行为一致。
3. 算法
Element
或 DocumentFragment
target,一个 Element
contextElement,一个 字符串 html,一个
字典 options 和一个 布尔值 safe:
-
如果 safe 为真且 contextElement 的 本地名 为 "
script",且 contextElement 的 命名空间为 HTML 命名空间 或 SVG 命名空间,则返回。 -
令 sanitizer 为调用 根据 options 获取 sanitizer 实例 的结果,参数为 options 和 safe。
-
令 newChildren 为根据 HTML 片段解析算法,传入 contextElement、html 和 true 的结果。
-
令 fragment 为一个新的
DocumentFragment, 其 节点文档为 contextElement 的 节点文档。 -
对 fragment 使用 sanitizer 和 safe 执行 sanitize。
-
在 target 内 用 fragment 替换所有。
注意: 该算法同时适用于 SetHTMLOptions
和
SetHTMLUnsafeOptions。
它们仅在默认值上有所不同。
3.1. 净化
ParentNode
node、一个
Sanitizer
sanitizer,以及一个 boolean safe,执行以下步骤:
-
令 configuration 为 sanitizer 的 configuration 的值。
-
如果 safe 为 true,则将 configuration 设为在 configuration 上调用 remove unsafe 的结果。
-
在 node、configuration 上调用 sanitize core, 并将 handleJavascriptNavigationUrls 设为 safe。
ParentNode
node、一个 SanitizerConfig
configuration,以及一个
boolean
handleJavascriptNavigationUrls,从
node 开始递归遍历 DOM 树。其步骤如下:
-
对于每个 child 属于 node 的 children:
-
断言:child 实现了
Text、Comment、Element或DocumentType。注意: 目前,此算法仅在 HTML 解析器的输出上被调用,在该情形下该断言应当成立。
DocumentType只应出现在parseHTML和parseHTMLUnsafe中。若未来该算法用于不同的上下文,则需要重新审视这一假设。 -
如果 child 实现了
DocumentType, 则继续。 -
否则:
-
令 elementName 为一个
SanitizerElementNamespace, 其由 child 的 本地名称 与 命名空间 组成。 -
如果 configuration["
replaceWithChildrenElements"] 存在, 且 configuration["replaceWithChildrenElements"] 包含 elementName:-
在 child 上,以 configuration 和 handleJavascriptNavigationUrls 调用 sanitize core。
-
调用 replace all,以 child 的 children 替换 child 内的所有内容。
-
继续。
-
-
否则:
-
如果 configuration["
removeElements"] 包含 elementName:
-
-
如果 elementName 等于 «[ "
name" → "template", "namespace" → HTML 命名空间 ]», 则在 child 的 template contents 上,以 configuration 与 handleJavascriptNavigationUrls 调用 sanitize core。 -
如果 child 是一个 shadow host, 则在 child 的 shadow root 上,以 configuration 与 handleJavascriptNavigationUrls 调用 sanitize core。
-
令 elementWithLocalAttributes 为 « [] »。
-
如果 configuration["
elements"] 存在 且 configuration["elements"] 包含 elementName:-
将 elementWithLocalAttributes 设为 configuration["
elements"][elementName]。
-
-
对于每个 attribute 属于 child 的 属性列表:
-
令 attrName 为一个
SanitizerAttributeNamespace, 其由 attribute 的 本地名称 与 命名空间 组成。 -
如果 elementWithLocalAttributes["
removeAttributes"] 带默认值 « » 包含 attrName:-
移除 attribute。
-
-
否则,若 configuration["
attributes"] 存在:-
如果 configuration["
attributes"] 不 包含 attrName,且 elementWithLocalAttributes["attributes"] 带默认值 « » 不 包含 attrName,并且如果 "data-" 不是 代码单元前缀(针对 attribute 的 本地名称), 且 命名空间 不为null,或者 configuration["dataAttributes"] 不为 true:-
移除 attribute。
-
-
-
否则:
-
如果 elementWithLocalAttributes["
attributes"] 存在 且 elementWithLocalAttributes["attributes"] 不 包含 attrName:-
移除 attribute。
-
-
否则,若 configuration["
removeAttributes"] 包含 attrName:-
移除 attribute。
-
-
-
如果 handleJavascriptNavigationUrls:
-
如果 «[elementName, attrName]» 匹配 内置“导航型 URL 属性”列表 中的一项,且 attribute 包含一个 javascript: URL,则移除 attribute。
-
如果 child 的 命名空间 是 MathML 命名空间 且 attr 的 本地名称 为 "
href",且 attr 的 命名空间 为null或 XLink 命名空间,并且 attr 包含一个 javascript: URL, 则移除 attribute。 -
如果 内置“动画型 URL 属性”列表 包含 «[elementName, attrName]»,且 attr 的 值 为 "
href" 或 "xlink:href",则移除 attribute。
-
-
-
在 child 上,以 configuration 与 handleJavascriptNavigationUrls 调用 sanitize core。
-
-
javascript: URL。
由于导航本身不是 XSS 威胁,我们只处理导航到 javascript: URL,不处理一般的导航。
声明式导航主要包括以下几类:
前两类由 内置导航 URL 属性列表 覆盖。
MathML 情况单独处理,因为本规范中没有"按命名空间全局"规则的形式化描述。
SVG 动画情况由
内置动画 URL 属性列表覆盖。但由于 SVG
动画元素的解释依赖于动画目标,且净化时无法知道最终目标,sanitize
算法会阻止对 href 属性的任何动画。
-
令 url 为对 attribute 的 值,执行 基本 URL 解析器 的结果。
-
如果 url 为
failure,返回 false。
3.2. 修改配置
配置修改方法是 Sanitizer
上用于修改其配置的方法。
这些方法会保持有效性准则。
它们返回布尔值,指示调用者配置是否已被修改。
let s= new Sanitizer({ elements: [ "div" ]}); s. allowElement( "p" ); // 返回 true。 div. setHTML( "<div><p>" , { sanitizer: s}); // 允许 `<div>` 和 `<p>`。
let s= new Sanitizer({ elements: [ "div" ]}); s. removeElement( "p" ); // 返回 false,因为 <p> 之前未被允许。 div. setHTML( "<div><p>" , { sanitizer: s}); // 允许 `<div>`,`<p>` 被移除。
SanitizerConfig
configuration 中
移除一个元素 SanitizerElement
element:
-
我们是拥有全局允许列表还是全局移除列表,
-
以及它们是否已经包含 element。
-
将 element 设为以 规范化一个 sanitizer 元素 处理 element 的结果。
-
将 modified 设为从 移除 element 自 configuration["
replaceWithChildrenElements"] 的结果。 -
否则:
-
如果 configuration["
removeElements"] 包含 element:-
注释:我们拥有全局移除列表且它已经包含 element。
-
返回 modified。
-
-
注释:我们拥有全局移除列表且它不包含 element。
-
添加 element 至 configuration["
removeElements"]。 -
返回 true。
-
SanitizerConfig
configuration 中
移除一个属性 SanitizerAttribute attribute:
注意: 此方法区分两种情况,即我们拥有全局允许列表或全局移除列表。 如果我们将 attribute 添加到全局移除列表,我们可能需要执行额外工作来修正按元素的允许或移除列表,以维持有效性准则。 如果我们从全局允许列表中移除 attribute,我们也可能需要从局部移除列表中移除它。
-
将 attribute 设为以 规范化一个 sanitizer 属性 处理 attribute 的结果。
-
如果 configuration["
attributes"] 存在:-
注释:若我们有全局允许列表,我们需要添加 attribute。
-
如果 configuration["
attributes"] 不 包含 attribute:-
返回 false。
-
-
注释:修正按元素的允许与移除列表。
-
如果 configuration["
elements"] 存在:-
对于每个 element 属于 configuration["
elements"]:-
如果 element["
removeAttributes"] 带默认值 « » 包含 attribute:-
从中移除 attribute 自 element["
removeAttributes"]。
-
-
-
-
从中移除 attribute 自 configuration["
attributes"]。 -
返回 true。
-
-
否则:
-
注释:若我们有全局移除列表,我们需要添加 attribute。
-
如果 configuration["
removeAttributes"] 包含 attribute 返回 false。 -
注释:修正按元素的允许与移除列表。
-
如果 configuration["
elements"] 存在:-
对于每个 element 属于 configuration["
elements"]:-
如果 element["
attributes"] 带默认值 « » 包含 attribute:-
从中移除 attribute 自 element["
attributes"]。
-
-
如果 element["
removeAttributes"] 带默认值 « » 包含 attribute:-
从中移除 attribute 自 element["
removeAttributes"]。
-
-
-
-
追加 attribute 至 configuration["
removeAttributes"] -
返回 true。
-
SanitizerConfig
configuration 中
移除不安全内容,执行如下操作:
注意: 虽然该算法称为 remove unsafe,我们对“unsafe”一词的使用 严格依据本规范中的含义,用以指代在插入文档时会执行 JavaScript 的内容。 换言之,该方法将消除 XSS 的机会。
-
断言:键集合(内置安全基线配置的)等于 «[ "
removeElements", "removeAttributes" ] »。 -
令 result 为 false。
-
对于每个 element 属于 内置安全基线配置[
removeElements]:-
在 configuration 上调用 移除一个元素 element。
-
如果调用返回 true,则将 result 设为 true。
-
-
对于每个 attribute 属于 内置安全基线配置[
removeAttributes]:-
在 configuration 上调用 移除一个属性 attribute。
-
如果调用返回 true,则将 result 设为 true。
-
-
对于每个 列于 事件处理器内容属性 的 attribute:
-
在 configuration 上调用 移除一个属性 attribute。
-
如果调用返回 true,则将 result 设为 true。
-
-
返回 result。
3.3. 设置配置
Sanitizer
sanitizer:
-
用 allowCommentsAndDataAttributes 对 configuration 执行 规范化。
-
如果 configuration 不是 有效配置,则返回 false。
-
将 sanitizer 的 configuration 设为 configuration。
-
返回 true。
3.4. 规范化配置
Sanitizer
以规范化形式存储configuration,这样能简化许多处理流程。
elements
列表 {elements: ["div"]} 会被存储为
{elements: [{name: "div", namespace: "http://www.w3.org/1999/xhtml"}]。
SanitizerConfig
configuration
且带有一个 布尔值
allowCommentsAndDataAttributes:
Note: 我们假设 configuration 是 [WebIDL]
将 JavaScript 值转换为 SanitizerConfig
的结果。
-
如果 configuration["
elements"] 与 configuration["removeElements"] 均不存在,则设置 configuration["removeElements"] 为 « »。 -
如果 configuration["
attributes"] 与 configuration["removeAttributes"] 均不存在,则设置 configuration["removeAttributes"] 为 « »。 -
如果 configuration["
elements"] 存在:-
令 elements 为 « »。
-
对于每个 element 属于 configuration["
elements"], 执行:-
追加 canonicalize a sanitizer element with attributes element 的结果到 elements。
-
-
将 configuration["
elements"] 设为 elements。
-
-
如果 configuration["
removeElements"] 存在:-
令 elements 为 « »。
-
对于每个 element 属于 configuration["
removeElements"], 执行:-
追加 canonicalize a sanitizer element element 的结果到 elements。
-
-
将 configuration["
removeElements"] 设为 elements。
-
-
如果 configuration["
replaceWithChildrenElements"] 存在:-
令 elements 为 « »。
-
对于每个 element 属于 configuration["
replaceWithChildrenElements"], 执行:-
追加 canonicalize a sanitizer element element 的结果到 elements。
-
-
将 configuration["
replaceWithChildrenElements"] 设为 elements。
-
-
如果 configuration["
attributes"] 存在:-
令 attributes 为 « »。
-
对于每个 attribute 属于 configuration["
attributes"], 执行:-
追加 canonicalize a sanitizer attribute attribute 的结果到 attributes。
-
-
将 configuration["
attributes"] 设为 attributes。
-
-
如果 configuration["
removeAttributes"] 存在:-
令 attributes 为 « »。
-
对于每个 attribute 属于 configuration["
removeAttributes"], 执行:-
追加 canonicalize a sanitizer attribute attribute 的结果到 attributes。
-
-
将 configuration["
removeAttributes"] 设为 attributes。
-
-
如果 configuration["
comments"] 不存在,则设置 configuration["comments"] 为 allowCommentsAndDataAttributes。 -
如果 configuration["
attributes"] 存在 且 configuration["dataAttributes"] 不存在,则 设置 configuration["dataAttributes"] 为 allowCommentsAndDataAttributes。
SanitizerElementWithAttributes
element:
-
令 result 为以 canonicalize a sanitizer element 处理 element 的结果。
-
如果 element 是一个 字典:
-
如果 element["
attributes"] 存在:-
令 attributes 为 « »。
-
对于每个 attribute 属于 element["
attributes"]:-
追加 以 canonicalize a sanitizer attribute 处理 attribute 的结果至 attributes。
-
-
设置 result["
attributes"] 为 attributes。
-
-
如果 element["
removeAttributes"] 存在:-
令 attributes 为 « »。
-
对于每个 attribute 属于 element["
removeAttributes"]:-
追加 以 canonicalize a sanitizer attribute 处理 attribute 的结果至 attributes。
-
-
设置 result["
removeAttributes"] 为 attributes。
-
-
-
如果 result["
attributes"] 与 result["removeAttributes"] 均不存在:-
设置 result["
removeAttributes"] 为 « »。
-
-
返回 result。
SanitizerElement
element,
返回以 canonicalize a sanitizer name 处理 element 且以
HTML 命名空间 作为默认命名空间的结果。
SanitizerAttribute
attribute,
返回以 canonicalize a sanitizer name 处理 attribute
且以 null 作为默认命名空间的结果。
3.5. 辅助算法
对于本规范中使用的 规范化
元素
和 属性名
列表,成员资格基于同时匹配 "name" 和
"namespace"
两个条目:
SanitizerElement
的列表 A 与
B 的交集,
等价于 集合交集,
但集合条目需先规范化:
-
令 set A 为 « [] »
-
令 set B 为 « [] »
-
遍历 A,将 规范化 sanitizer 名称 得到的 entry 添加至 set A。
-
遍历 B,将 规范化 sanitizer 名称 得到的 entry 添加至 set B。
3.6. 内置项
共有四项内置项:
内置安全默认配置 如下:
{ "elements" : [ { "name" : "math" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "merror" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mfrac" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mi" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mmultiscripts" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mn" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mo" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [ { "name" : "fence" , "namespace" : null }, { "name" : "form" , "namespace" : null }, { "name" : "largeop" , "namespace" : null }, { "name" : "lspace" , "namespace" : null }, { "name" : "maxsize" , "namespace" : null }, { "name" : "minsize" , "namespace" : null }, { "name" : "movablelimits" , "namespace" : null }, { "name" : "rspace" , "namespace" : null }, { "name" : "separator" , "namespace" : null }, { "name" : "stretchy" , "namespace" : null }, { "name" : "symmetric" , "namespace" : null } ] }, { "name" : "mover" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [ { "name" : "accent" , "namespace" : null } ] }, { "name" : "mpadded" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [ { "name" : "depth" , "namespace" : null }, { "name" : "height" , "namespace" : null }, { "name" : "lspace" , "namespace" : null }, { "name" : "voffset" , "namespace" : null }, { "name" : "width" , "namespace" : null } ] }, { "name" : "mphantom" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mprescripts" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mroot" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mrow" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "ms" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mspace" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [ { "name" : "depth" , "namespace" : null }, { "name" : "height" , "namespace" : null }, { "name" : "width" , "namespace" : null } ] }, { "name" : "msqrt" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mstyle" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "msub" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "msubsup" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "msup" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mtable" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mtd" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [ { "name" : "columnspan" , "namespace" : null }, { "name" : "rowspan" , "namespace" : null } ] }, { "name" : "mtext" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mtr" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "munder" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [ { "name" : "accentunder" , "namespace" : null } ] }, { "name" : "munderover" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [ { "name" : "accent" , "namespace" : null }, { "name" : "accentunder" , "namespace" : null } ] }, { "name" : "semantics" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "a" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "href" , "namespace" : null }, { "name" : "hreflang" , "namespace" : null }, { "name" : "type" , "namespace" : null } ] }, { "name" : "abbr" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "address" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "article" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "aside" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "b" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "bdi" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "bdo" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "blockquote" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "cite" , "namespace" : null } ] }, { "name" : "body" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "br" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "caption" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "cite" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "code" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "col" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "span" , "namespace" : null } ] }, { "name" : "colgroup" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "span" , "namespace" : null } ] }, { "name" : "data" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "value" , "namespace" : null } ] }, { "name" : "dd" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "del" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "cite" , "namespace" : null }, { "name" : "datetime" , "namespace" : null } ] }, { "name" : "dfn" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "div" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "dl" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "dt" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "em" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "figcaption" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "figure" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "footer" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "h1" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "h2" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "h3" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "h4" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "h5" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "h6" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "head" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "header" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "hgroup" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "hr" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "html" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "i" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "ins" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "cite" , "namespace" : null }, { "name" : "datetime" , "namespace" : null } ] }, { "name" : "kbd" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "li" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "value" , "namespace" : null } ] }, { "name" : "main" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "mark" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "menu" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "nav" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "ol" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "reversed" , "namespace" : null }, { "name" : "start" , "namespace" : null }, { "name" : "type" , "namespace" : null } ] }, { "name" : "p" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "pre" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "q" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "rp" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "rt" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "ruby" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "s" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "samp" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "search" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "section" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "small" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "span" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "strong" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "sub" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "sup" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "table" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "tbody" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "td" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "colspan" , "namespace" : null }, { "name" : "headers" , "namespace" : null }, { "name" : "rowspan" , "namespace" : null } ] }, { "name" : "tfoot" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "th" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "abbr" , "namespace" : null }, { "name" : "colspan" , "namespace" : null }, { "name" : "headers" , "namespace" : null }, { "name" : "rowspan" , "namespace" : null }, { "name" : "scope" , "namespace" : null } ] }, { "name" : "thead" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "time" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "datetime" , "namespace" : null } ] }, { "name" : "title" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "tr" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "u" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "ul" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "var" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "wbr" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "circle" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "cx" , "namespace" : null }, { "name" : "cy" , "namespace" : null }, { "name" : "pathLength" , "namespace" : null }, { "name" : "r" , "namespace" : null } ] }, { "name" : "defs" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [] }, { "name" : "desc" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [] }, { "name" : "ellipse" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "cx" , "namespace" : null }, { "name" : "cy" , "namespace" : null }, { "name" : "pathLength" , "namespace" : null }, { "name" : "rx" , "namespace" : null }, { "name" : "ry" , "namespace" : null } ] }, { "name" : "foreignObject" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "height" , "namespace" : null }, { "name" : "width" , "namespace" : null }, { "name" : "x" , "namespace" : null }, { "name" : "y" , "namespace" : null } ] }, { "name" : "g" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [] }, { "name" : "line" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "pathLength" , "namespace" : null }, { "name" : "x1" , "namespace" : null }, { "name" : "x2" , "namespace" : null }, { "name" : "y1" , "namespace" : null }, { "name" : "y2" , "namespace" : null } ] }, { "name" : "marker" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "markerHeight" , "namespace" : null }, { "name" : "markerUnits" , "namespace" : null }, { "name" : "markerWidth" , "namespace" : null }, { "name" : "orient" , "namespace" : null }, { "name" : "preserveAspectRatio" , "namespace" : null }, { "name" : "refX" , "namespace" : null }, { "name" : "refY" , "namespace" : null }, { "name" : "viewBox" , "namespace" : null } ] }, { "name" : "metadata" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [] }, { "name" : "path" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "d" , "namespace" : null }, { "name" : "pathLength" , "namespace" : null } ] }, { "name" : "polygon" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "pathLength" , "namespace" : null }, { "name" : "points" , "namespace" : null } ] }, { "name" : "polyline" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "pathLength" , "namespace" : null }, { "name" : "points" , "namespace" : null } ] }, { "name" : "rect" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "height" , "namespace" : null }, { "name" : "pathLength" , "namespace" : null }, { "name" : "rx" , "namespace" : null }, { "name" : "ry" , "namespace" : null }, { "name" : "width" , "namespace" : null }, { "name" : "x" , "namespace" : null }, { "name" : "y" , "namespace" : null } ] }, { "name" : "svg" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "height" , "namespace" : null }, { "name" : "preserveAspectRatio" , "namespace" : null }, { "name" : "viewBox" , "namespace" : null }, { "name" : "width" , "namespace" : null }, { "name" : "x" , "namespace" : null }, { "name" : "y" , "namespace" : null } ] }, { "name" : "text" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "dx" , "namespace" : null }, { "name" : "dy" , "namespace" : null }, { "name" : "lengthAdjust" , "namespace" : null }, { "name" : "rotate" , "namespace" : null }, { "name" : "textLength" , "namespace" : null }, { "name" : "x" , "namespace" : null }, { "name" : "y" , "namespace" : null } ] }, { "name" : "textPath" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "lengthAdjust" , "namespace" : null }, { "name" : "method" , "namespace" : null }, { "name" : "path" , "namespace" : null }, { "name" : "side" , "namespace" : null }, { "name" : "spacing" , "namespace" : null }, { "name" : "startOffset" , "namespace" : null }, { "name" : "textLength" , "namespace" : null } ] }, { "name" : "title" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [] }, { "name" : "tspan" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "dx" , "namespace" : null }, { "name" : "dy" , "namespace" : null }, { "name" : "lengthAdjust" , "namespace" : null }, { "name" : "rotate" , "namespace" : null }, { "name" : "textLength" , "namespace" : null }, { "name" : "x" , "namespace" : null }, { "name" : "y" , "namespace" : null } ] } ], "attributes" : [ { "name" : "alignment-baseline" , "namespace" : null }, { "name" : "baseline-shift" , "namespace" : null }, { "name" : "clip-path" , "namespace" : null }, { "name" : "clip-rule" , "namespace" : null }, { "name" : "color" , "namespace" : null }, { "name" : "color-interpolation" , "namespace" : null }, { "name" : "cursor" , "namespace" : null }, { "name" : "dir" , "namespace" : null }, { "name" : "direction" , "namespace" : null }, { "name" : "display" , "namespace" : null }, { "name" : "displaystyle" , "namespace" : null }, { "name" : "dominant-baseline" , "namespace" : null }, { "name" : "fill" , "namespace" : null }, { "name" : "fill-opacity" , "namespace" : null }, { "name" : "fill-rule" , "namespace" : null }, { "name" : "font-family" , "namespace" : null }, { "name" : "font-size" , "namespace" : null }, { "name" : "font-size-adjust" , "namespace" : null }, { "name" : "font-stretch" , "namespace" : null }, { "name" : "font-style" , "namespace" : null }, { "name" : "font-variant" , "namespace" : null }, { "name" : "font-weight" , "namespace" : null }, { "name" : "lang" , "namespace" : null }, { "name" : "letter-spacing" , "namespace" : null }, { "name" : "marker-end" , "namespace" : null }, { "name" : "marker-mid" , "namespace" : null }, { "name" : "marker-start" , "namespace" : null }, { "name" : "mathbackground" , "namespace" : null }, { "name" : "mathcolor" , "namespace" : null }, { "name" : "mathsize" , "namespace" : null }, { "name" : "opacity" , "namespace" : null }, { "name" : "paint-order" , "namespace" : null }, { "name" : "pointer-events" , "namespace" : null }, { "name" : "scriptlevel" , "namespace" : null }, { "name" : "shape-rendering" , "namespace" : null }, { "name" : "stop-color" , "namespace" : null }, { "name" : "stop-opacity" , "namespace" : null }, { "name" : "stroke" , "namespace" : null }, { "name" : "stroke-dasharray" , "namespace" : null }, { "name" : "stroke-dashoffset" , "namespace" : null }, { "name" : "stroke-linecap" , "namespace" : null }, { "name" : "stroke-linejoin" , "namespace" : null }, { "name" : "stroke-miterlimit" , "namespace" : null }, { "name" : "stroke-opacity" , "namespace" : null }, { "name" : "stroke-width" , "namespace" : null }, { "name" : "text-anchor" , "namespace" : null }, { "name" : "text-decoration" , "namespace" : null }, { "name" : "text-overflow" , "namespace" : null }, { "name" : "text-rendering" , "namespace" : null }, { "name" : "title" , "namespace" : null }, { "name" : "transform" , "namespace" : null }, { "name" : "transform-origin" , "namespace" : null }, { "name" : "unicode-bidi" , "namespace" : null }, { "name" : "vector-effect" , "namespace" : null }, { "name" : "visibility" , "namespace" : null }, { "name" : "white-space" , "namespace" : null }, { "name" : "word-spacing" , "namespace" : null }, { "name" : "writing-mode" , "namespace" : null } ], "comments" : false , "dataAttributes" : false }
注意:所包含的 [MathML] 标记基于 [SafeMathML]。
内置安全基线配置旨在仅阻止脚本内容。具体如下:
{ "removeElements" : [ { "namespace" : "http://www.w3.org/1999/xhtml" , "name" : "embed" }, { "namespace" : "http://www.w3.org/1999/xhtml" , "name" : "frame" }, { "namespace" : "http://www.w3.org/1999/xhtml" , "name" : "iframe" }, { "namespace" : "http://www.w3.org/1999/xhtml" , "name" : "object" }, { "namespace" : "http://www.w3.org/1999/xhtml" , "name" : "script" }, { "namespace" : "http://www.w3.org/2000/svg" , "name" : "script" }, { "namespace" : "http://www.w3.org/2000/svg" , "name" : "use" } ], "removeAttributes" : [] }
警告: remove unsafe 算法还规定要额外移除所有 事件处理内容属性,详见 [HTML]。 如果某个 用户代理 在 [HTML] 规范基础上扩展了额外的 事件处理内容属性,则如何处理这些属性由其自行决定。基于当前的 事件处理内容属性 列表, 安全基线配置实际上大致如下所示:
{ "removeElements" : [ { "namespace" : "http://www.w3.org/1999/xhtml" , "name" : "embed" }, { "namespace" : "http://www.w3.org/1999/xhtml" , "name" : "frame" }, { "namespace" : "http://www.w3.org/1999/xhtml" , "name" : "iframe" }, { "namespace" : "http://www.w3.org/1999/xhtml" , "name" : "object" }, { "namespace" : "http://www.w3.org/1999/xhtml" , "name" : "script" }, { "namespace" : "http://www.w3.org/2000/svg" , "name" : "script" }, { "namespace" : "http://www.w3.org/2000/svg" , "name" : "use" } ], "removeAttributes" : [ "onafterprint" , "onauxclick" , "onbeforeinput" , "onbeforematch" , "onbeforeprint" , "onbeforeunload" , "onbeforetoggle" , "onblur" , "oncancel" , "oncanplay" , "oncanplaythrough" , "onchange" , "onclick" , "onclose" , "oncontextlost" , "oncontextmenu" , "oncontextrestored" , "oncopy" , "oncuechange" , "oncut" , "ondblclick" , "ondrag" , "ondragend" , "ondragenter" , "ondragleave" , "ondragover" , "ondragstart" , "ondrop" , "ondurationchange" , "onemptied" , "onended" , "onerror" , "onfocus" , "onformdata" , "onhashchange" , "oninput" , "oninvalid" , "onkeydown" , "onkeypress" , "onkeyup" , "onlanguagechange" , "onload" , "onloadeddata" , "onloadedmetadata" , "onloadstart" , "onmessage" , "onmessageerror" , "onmousedown" , "onmouseenter" , "onmouseleave" , "onmousemove" , "onmouseout" , "onmouseover" , "onmouseup" , "onoffline" , "ononline" , "onpagehide" , "onpagereveal" , "onpageshow" , "onpageswap" , "onpaste" , "onpause" , "onplay" , "onplaying" , "onpopstate" , "onprogress" , "onratechange" , "onreset" , "onresize" , "onrejectionhandled" , "onscroll" , "onscrollend" , "onsecuritypolicyviolation" , "onseeked" , "onseeking" , "onselect" , "onslotchange" , "onstalled" , "onstorage" , "onsubmit" , "onsuspend" , "ontimeupdate" , "ontoggle" , "onunhandledrejection" , "onunload" , "onvolumechange" , "onwaiting" , "onwheel" ] }
javascript:" 导航被视为“不安全”,内容如下:
«[
[
{ "name" → "a", "namespace" → HTML 命名空间
},
{ "name" → "href", "namespace" → null }
],
[
{ "name" → "area", "namespace" → HTML 命名空间
},
{ "name" → "href", "namespace" → null }
],
[
{ "name" → "base", "namespace" → HTML 命名空间
},
{ "name" → "href", "namespace" → null }
],
[
{ "name" → "button", "namespace" → HTML 命名空间
},
{ "name" → "formaction", "namespace" → null }
],
[
{ "name" → "form", "namespace" → HTML 命名空间
},
{ "name" → "action", "namespace" → null }
],
[
{ "name" → "iframe", "namespace" → HTML 命名空间
},
{ "name" → "src", "namespace" → null }
],
[
{ "name" → "input", "namespace" → HTML 命名空间
},
{ "name" → "formaction", "namespace" → null }
],
[
{ "name" → "a", "namespace" → SVG 命名空间 },
{ "name" → "href", "namespace" → null }
],
[
{ "name" → "a", "namespace" → SVG 命名空间 },
{ "name" → "href", "namespace" → XLink 命名空间 }
],
]»
内置动画
URL 属性列表,可用于
[SVG11] 中以声明方式修改导航元素以使用
"javascript:" URL,内容如下:
«[
[
{ "name" → "animate", "namespace" → SVG 命名空间 },
{ "name" → "attributeName", "namespace" → null] }
],
[
{ "name" → "animateMotion", "namespace" → SVG 命名空间 },
{ "name" → "attributeName", "namespace" → null }
],
[
{ "name" → "animateTransform", "namespace" → SVG 命名空间 },
{ "name" → "attributeName", "namespace" → null }
],
[
{ "name" → "set", "namespace" → SVG 命名空间 },
{ "name" → "attributeName", "namespace" → null }
],
]»
4. 安全注意事项
Sanitizer API 旨在通过遍历所提供的 HTML 内容,并根据配置移除元素和属性,防止基于 DOM 的跨站脚本(XSS)攻击。规范要求,API 不应支持构造会遗留可执行脚本标记的 Sanitizer 对象,否则将会破坏威胁模型。
但即便如此,正确使用 Sanitizer API 也无法防护所有安全问题,相关场景会在下文中说明。
4.1. 服务端反射型和存储型 XSS
本节为非规范性内容。
Sanitizer API 仅在 DOM 中操作,提供遍历和过滤现有 DocumentFragment 的能力。Sanitizer 不涉及服务端反射型或存储型 XSS 问题。
4.2. DOM 覆写攻击(clobbering)
本节为非规范性内容。
DOM 覆写攻击指通过恶意 HTML 使用 id 或 name 属性命名元素,从而让 HTML 元素的某些属性(如
children)被恶意内容覆盖,导致应用混淆。
Sanitizer API 的默认行为无法防护 DOM 覆写攻击,但可以通过配置移除 id 和 name 属性。
4.3. 利用脚本组件的 XSS
本节为非规范性内容。
脚本组件攻击是一种攻击者利用流行 JavaScript 库中的已有应用代码,通过注入看似无害的代码或仅被框架解析的 DOM 节点,最终由框架解析并执行这些输入中的 JavaScript 的技术。
Sanitizer API 无法防止此类攻击,要求页面作者一般要显式允许未知元素,并且还需针对未知属性、元素及如 data-、slot 属性和
<slot>、<template> 等广泛用于模板和框架的标记作出明确配置。
我们认为这些限制并不详尽,鼓励页面作者检查所用第三方库是否有此类行为。
4.4. 变异型 XSS(mXSS)
本节为非规范性内容。
变异型 XSS(mXSS)描述的是由于解析器上下文不一致,在错误上下文下解析 HTML 片段时发生攻击。当某个已解析的 HTML 片段序列化为字符串后,在插入到不同父元素时未必能保证解析效果一致。例如,依赖于外部内容或标签嵌套错误导致解析行为变化,即可发动此类攻击。
Sanitizer API 只提供将字符串转为节点树的功能。上下文由所有 sanitizer 函数隐式提供:Element.setHTML()
使用当前元素;Document.parseHTML() 创建新文档。因此 Sanitizer API 不会直接受到变异型 XSS 影响。
如果开发者将净化后的节点树通过 .innerHTML 取字符串,再重新解析,则有可能发生变异型 XSS。我们不推荐这种做法。如果确实需要处理或传递 HTML
字符串,则应将其视为不可信内容,并在插入 DOM 时重新净化。换言之,净化并序列化后的 HTML 树不应再被视作已净化。
关于 mXSS 的更完整讨论可见 [MXSS]。
5. 致谢
本项工作借鉴了 cure53 团队的 [DOMPURIFY],
以及 Internet Explorer 的 window.toStaticHTML()
和 Ben Bucksch 的
[HTMLSanitizer]。感谢 Anne
van Kesteren、Krzysztof Kotowicz、
Andrew C. H. Mc Millan、Tom Schuster、Luke Warlow、Guillaume Weghsteen
以及 Mike West 的宝贵反馈。