1. 介绍
本节不具备规范性。
Web 应用通常需要在客户端处理 HTML 字符串,
可能用在客户端模板解决方案,也可能用于渲染用户生成的内容等场景。安全地处理这些字符串非常困难。
简单地拼接字符串并插入到
Element
的
innerHTML
属性中,这种做法风险极高,因为可能会以各种意想不到的方式导致 JavaScript 执行。
像 [DOMPURIFY] 这样的库 试图通过在插入前仔细解析和净化字符串来解决这个问题,方法是构建 DOM 并根据允许列表过滤成员。 但这种做法很脆弱,Web 暴露的解析 API 与浏览器实际渲染字符串为 HTML 时的行为并不总能合理对应。 此外,这些库还需要不断跟踪浏览器行为的变化;曾经安全的做法可能因新平台特性变得危险。
浏览器本身对何时执行代码有相当清晰的判断。 我们可以通过让浏览器自身以安全方式渲染任意 HTML 字符串,从而改进用户空间库, 并且这种方式更有可能与浏览器解析器的持续更新保持同步。 本文档描述了一个旨在实现这一目标的 API。
1.1. 目标
-
通过为开发者提供处理用户控制的 HTML 的机制,防止直接脚本执行,从而缓解基于 DOM 的跨站脚本攻击风险。
-
确保生成的 HTML 输出在当前用户代理环境下是安全的,并考虑其对 HTML 的最新理解。
-
允许开发者自定义默认的元素和属性集合。 添加特定的元素和属性可以防止 脚本小工具 攻击。
1.2. API 概述
Sanitizer API 提供将包含 HTML 的字符串解析为 DOM 树,并根据用户配置过滤结果树的功能。 方法有两种风格:
-
安全与不安全: “安全”方法不会生成任何能执行脚本的标记,即应当能防止 XSS。 “不安全”方法会按要求解析和过滤内容。 参见:第 4 节 安全注意事项。
-
上下文:方法定义在
Element
和ShadowRoot
上,会替换这些Node
的子节点,整体类似于innerHTML
。 也有定义在Document
上的静态方法, 用于解析整个文档,整体类似于DOMParser
的parseFromString()
。
2. 框架
2.1. Sanitizer 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 为调用 获取受信类型兼容字符串 算法的结果, 参数为
TrustedHTML
、 this 的 相关全局对象、html、"Element setHTMLUnsafe" 和 "script"。 -
令 target 为 this 的 template contents(如果 this 是
template
元素);否则为 this。 -
根据 target、this、compliantHTML、options 和 false 调用 设置并过滤 HTML。
Element
的
setHTML(html, options) 方法步骤如下:
-
令 target 为 this 的 template contents(如果 this 是
template
元素);否则为 this。 -
根据 target、this、html、options 和 true 调用 设置并过滤 HTML。
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 为调用 获取受信类型兼容字符串 算法的结果, 参数为
TrustedHTML
、 this 的 相关全局对象、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
-
令 compliantHTML 为调用 获取受信类型兼容字符串 算法的结果, 参数为
TrustedHTML
、 this 的 相关全局对象、html、"Document parseHTMLUnsafe" 和 "script"。 -
令 document 为新的
Document
, 其 内容类型 为 "text/html"。注意:由于 document 没有浏览上下文,脚本是禁用的。
-
设置 document 的 允许声明式 shadow root 为 true。
-
根据 document 和 compliantHTML,从字符串解析 HTML。
-
令 sanitizer 为调用 根据 options 获取 sanitizier 实例 的结果,参数为 options 和 false。
-
在 document 上调用 sanitize,参数为 sanitizer 和 false。
-
返回 document。
-
令 document 为新的
Document
, 其 内容类型 为 "text/html"。注意:由于 document 没有浏览上下文,脚本是禁用的。
-
设置 document 的 允许声明式 shadow root 为 true。
-
根据 document 和 html,从字符串解析 HTML。
-
令 sanitizer 为调用 根据 options 获取 sanitizier 实例 的结果,参数为 options 和 true。
-
在 document 上调用 sanitize,参数为 sanitizer 和 true。
-
返回 document。
2.2. SetHTML 选项和配置对象
所有 setHTML()
类方法都接受一个 options 字典。当前只定义了字典的一个成员:
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"); // 查询配置:
configuration SanitizerConfig (); // 修改 Sanitizer 的列表和字段:
get 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 ); // 移除可执行脚本的标记
allow boolean (); };
removeUnsafe
Sanitizer
具有一个关联的 SanitizerConfig
configuration(配置)。
-
如果 configuration 是
SanitizerPresets
字符串,则: -
令 valid 为在 set a configuration 中 传入 configuration 和 true,作用于 this 的返回值。
-
如果 valid 为 false,则抛出
TypeError
。
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
。
空配置(当使用“不安全”方法如setHTMLUnsafe
时)允许所有内容。
配置值"default"
包含内置安全默认配置。
注意“安全”和“不安全”净化方法有不同的默认值。
并非所有配置字典都是有效的。有效的配置避免冗余(如两次允许同一个元素)和矛盾(如同一个元素既被允许又被移除)。
一个有效配置需要满足以下多个条件:
-
全局允许列表和移除列表混用:
-
elements
或removeElements
可以存在,但不能同时存在。 如果两者都缺失,则等同于removeElements
设为 « [] »。 -
attributes
或removeAttributes
可以存在,但不能同时存在。 如果两者都缺失,则等同于removeAttributes
设为 « [] »。 -
dataAttributes
从概念上是attributes
允许列表的扩展。只有使用了attributes
列表时,dataAttributes
属性才允许出现。
-
-
不同全局列表之间的重复项:
-
elements
、removeElements
、replaceWithChildrenElements
之间不能有重复项(即同一个元素)。 -
attributes
和removeAttributes
之间不能有重复项(即同一个属性)。
-
-
同一元素上的重复项:
-
同一元素上的
attributes
和removeAttributes
之间不能有重复项。
-
elements
元素允许列表也可以为某个元素指定允许或移除的属性。这与[HTML]的结构类似,HTML区分了全局属性和只适用于特定元素的局部属性。全局和局部属性可混合使用,但如果某个属性在一个列表允许,在另一个列表禁止,则该配置一般是无效的。
全局 attributes
| 全局 removeAttributes
| |
---|---|---|
局部 attributes
| 属性只要在任一列表中匹配就被允许。不允许重复项。 | 属性只有在局部允许列表中才被允许。全局移除与局部允许之间不能有重复。注意全局移除列表对该元素没有作用,但可能对没有局部允许列表的其它元素有作用。 |
局部 removeAttributes
| 属性只要在全局允许列表且不在局部移除列表就被允许。局部移除必须是全局允许列表的子集。 | 属性只有在两个列表都没有时才被允许。全局移除与局部移除之间不能有重复项。 |
请注意,大多数情况下全局与局部列表之间都不能有重复项,但当存在全局允许列表和局部移除列表时,局部移除列表必须是全局允许列表的子集。下面摘录了上表关于重复项的部分:
全局 attributes
| 全局 removeAttributes
| |
---|---|---|
局部 attributes
| 不允许重复项。 | 不允许重复项。 |
局部 removeAttributes
| 局部移除必须是全局允许列表的子集。 | 不允许重复项。 |
dataAttributes
设置允许自定义 data 属性。如将dataAttributes
视为允许列表,上述规则可直接扩展到自定义 data 属性:
全局 attributes
和 dataAttributes
已配置
| |
---|---|
局部 attributes
| 所有自定义 data 属性都被允许。不允许任何自定义 data 属性出现在任何允许列表,否则会出现重复项。 |
局部 removeAttributes
| 自定义 data 属性会被允许,除非它在局部移除列表中。 不允许任何自定义 data 属性出现在全局允许列表,否则会出现重复项。 |
用文字总结这些规则:
-
全局与局部列表之间的重复项与交互:
-
如果存在全局
attributes
允许列表,则所有元素的局部列表:-
如果存在局部
attributes
允许列表,则这些列表之间不得有重复项。 -
如果存在局部
removeAttributes
移除列表,则其所有条目也必须列在全局attributes
允许列表中。 -
如果
dataAttributes
为 true,则不得有自定义 data 属性出现在任何允许列表中。
-
-
如果存在全局
removeAttributes
移除列表,则:-
如果存在局部
attributes
允许列表,则这些列表之间不得有重复项。 -
如果存在局部
removeAttributes
移除列表,则这些列表之间不得有重复项。 -
dataAttributes
必须缺失。
-
-
SanitizerConfig
config 是 有效的,当且仅当以下所有条件成立:
-
config 要么有
elements
键,要么有removeElements
键,但不能两者都有。 -
config 要么有
attributes
键,要么有removeAttributes
键,但不能两者都有。 -
断言:config 中所有
SanitizerElementNamespaceWithAttributes
、SanitizerElementNamespace
, 以及SanitizerAttributeNamespace
项都是规范化的,即它们已根据需要经过 规范化元素 或 规范化属性 过程处理。 -
如果 config[
elements
]、 config[removeElements
]、 config[replaceWithChildrenElements
]、 config[attributes
], 或 config[removeAttributes
] 存在, 则它们都不应 有重复项。 -
如果 config[
elements
] 和 config[replaceWithChildrenElements
] 都存在,则 两者的交集必须是 空的。 -
如果 config[
removeElements
] 和 config[replaceWithChildrenElements
] 都存在,则 两者的交集必须是 空的。 -
如果 config[
attributes
] 存在:-
-
对于任意 element 属于 config[
elements
]:-
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
] 存在, 则 对于任意 element 属于 config[elements
]:-
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 上运行 sanitize, 使用 sanitizer 和 safe。
-
用 fragment 替换全部 target 内的内容。
注意:此算法适用于 SetHTMLOptions
和
SetHTMLUnsafeOptions
。
它们仅在默认值上有所区别。
-
令 sanitizerSpec 为 "
default
"。 -
-
将 sanitizerSpec 设为 options["
sanitizer
"]
-
-
断言:sanitizerSpec 必须是
Sanitizer
实例、 属于 字符串并是SanitizerPresets
成员,或一个字典。 -
如果 sanitizerSpec 是字符串:
-
如果 sanitizerSpec 是字典:
-
令 sanitizer 为一个新的
Sanitizer
实例。 -
令 setConfigurationResult 为在 sanitizer 上调用 set a configuration ,参数为 sanitizerSpec 和 非 safe。
-
将 sanitizerSpec 设为 sanitizer。
-
-
返回 sanitizerSpec。
3.1. 净化
ParentNode
node,
一个 Sanitizer
sanitizer,以及一个 boolean safe,运行以下步骤:
-
令 configuration 为 sanitizer 的 configuration 的值。
-
如果 safe 为 true,则将 configuration 设为调用 remove unsafe 于 configuration 的结果。
-
调用 sanitize core 于 node、configuration,并将 handleJavascriptNavigationUrls 设为 safe。
ParentNode
node,一个 SanitizerConfig
configuration,以及一个
boolean
handleJavascriptNavigationUrls,从 node
开始递归遍历 DOM 树。步骤如下:
-
-
断言:child 必须 实现
Text
、Comment
、Element
、 或DocumentType
。注意: 当前此算法仅用于 HTML 解析器输出,断言应成立。
DocumentType
只会在parseHTML
和parseHTMLUnsafe
过程中出现。若将来算法适用范围扩大,应重新评估此断言。 -
如果 child 实现
DocumentType
, 则 继续。 -
否则:
-
令 elementName 为
SanitizerElementNamespace
,其值为 child 的 本地名称 和 命名空间。 -
如果 configuration["
replaceWithChildrenElements
"] 存在 且 configuration["replaceWithChildrenElements
"] 包含 elementName:-
调用 sanitize core 于 child, 参数为 configuration 和 handleJavascriptNavigationUrls。
-
调用 replace all,用 child 的 子节点 替换 child。
-
继续。
-
-
如果 configuration["
removeElements
"] 存在 且 configuration["removeElements
"] 包含 elementName: -
如果 configuration["
elements
"] 存在 且 configuration["elements
"] 不包含 elementName: -
如果 elementName 等于 «[ "
name
" → "template
", "namespace
" → HTML 命名空间 ]», 则调用 sanitize core 于 child 的 template contents,参数为 configuration 和 handleJavascriptNavigationUrls。 -
如果 child 是shadow host, 则调用 sanitize core 于 child 的 shadow root,参数为 configuration 和 handleJavascriptNavigationUrls。
-
令 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。
-
-
-
调用 sanitize core 于 child, 参数为 configuration 和 handleJavascriptNavigationUrls。
-
-
javascript:
URL。
由于导航本身不是 XSS 威胁,我们只处理导航到 javascript:
URL,而不是一般导航。
声明式导航主要有以下几类:
前两类由 内置导航URL属性列表覆盖。
MathML 情况由单独规则覆盖,因为本规范没有“按命名空间全局”规则的形式化表达。
SVG 动画情况由 内置动画URL属性列表覆盖。但由于 SVG 动画元素解释依赖于动画目标,且净化过程中无法确定最终目标,sanitize 算法会阻止任何 href 属性的动画。
3.2. 修改配置
配置修饰方法是定义在 Sanitizer
上的用于修改其配置的方法。
它们会维护配置的有效性条件。
它们返回一个布尔值,告知调用方配置是否被修改。
let s= new Sanitizer({ elements: [ "div" ]}); s. allowElement( "p" ); // Returns true. div. setHTML( "<div><p>" , { sanitizer: s}); // Allows `<div>` and `<p>`.
let s= new Sanitizer({ elements: [ "div" ]}); s. removeElement( "p" ); // Return false, as <p> was not previously allowed. div. setHTML( "<div><p>" , { sanitizer: s}); // Allows `<div>`. `<p>` is removed.
SanitizerElementWithAttributes
element 在一个 SanitizerConfig
configuration 中执行以下操作:
-
我们是否有全局的允许列表或移除列表,和
-
这些列表中是否已经包含了 element。
-
将 element 设为 规范化带属性的清洗器元素 的结果, 以 element 作为参数。
-
如果 configuration["
elements
"] 存在:-
将 modified 设为 移除 element 从 configuration["
replaceWithChildrenElements
"] 的结果。 -
注释:需要确保每个元素的属性不与全局属性重叠。
-
如果 element["
attributes
"] 存在:-
如果 configuration["
attributes
"] 存在:-
将 element["
attributes
"] 设为 去重后的 element["attributes
"]。 -
将 element["
attributes
"] 设为 差集, 以 element["attributes
"] 和 configuration["attributes
"] 作为输入。 -
如果 configuration["
dataAttributes
"] 为 true:-
移除所有 item,从 element["
attributes
"] 中移除,其中 item 是 自定义数据属性。
-
-
-
如果 configuration["
removeAttributes
"] 存在:-
将 element["
attributes
"] 设为 差集, 以 element["attributes
"] 和 configuration["removeAttributes
"] 作为输入。
-
-
-
如果 element["
removeAttributes
"] 存在:-
将 element["
removeAttributes
"] 设为 去重后的 element["removeAttributes
"]。 -
如果 configuration["
attributes
"] 存在:-
将 element["
removeAttributes
"] 设为 交集, 以 element["removeAttributes
"] 和 configuration["attributes
"] 作为输入。
-
-
如果 configuration["
removeAttributes
"] 存在:-
将 element["
removeAttributes
"] 设为 差集, 以 element["removeAttributes
"] 和 configuration["removeAttributes
"] 作为输入。
-
-
-
注释:这是全局允许列表已经包含 element 的情况。
-
令 current element 为 configuration["
elements
"] 中满足 item[name
] 等于 element[name
] 且 item[namespace
] 等于 element[namespace
] 的 item。 -
如果 element 等于 current element,则返回 modified。
-
返回 true。
-
-
否则:
-
如果 element["
attributes
"] 或 element["removeAttributes
"] 存在:-
用户代理可以 向控制台报告警告,说明此操作不被支持。
-
返回 false。
-
-
将 modified 设为 移除 element 从 configuration["
replaceWithChildrenElements
"] 的结果。 -
如果 configuration["
removeElements
"] 不 包含 element:-
注释:这是全局移除列表尚未包含 element 的情况。
-
返回 modified。
-
-
注释:这是全局移除列表已包含 element 的情况。
-
移除 element 从 configuration["
removeElements
"]。 -
返回 true。
-
SanitizerElement
element
从 SanitizerConfig
configuration 中:
-
我们有全局允许列表还是全局移除列表,
-
它们是否已经包含 element。
-
将 element 设为 规范化清洗器元素 的结果,参数为 element。
-
将 modified 设为 移除 element 从 configuration["
replaceWithChildrenElements
"] 的结果。 -
否则:
-
如果 configuration["
removeElements
"] 包含 element:-
注释:我们有全局移除列表且已包含 element。
-
返回 modified。
-
-
注释:我们有全局移除列表但未包含 element。
-
添加 element 到 configuration["
removeElements
"]。 -
返回 true。
-
SanitizerElement
element 从 SanitizerConfig
configuration 中:
-
将 element 设为 规范化清洗器元素 的结果,参数为 element。
-
如果 configuration["
replaceWithChildrenElements
"] 包含 element:-
返回 false。
-
-
移除 element 从 configuration["
removeElements
"]。 -
添加 element 到 configuration["
replaceWithChildrenElements
"]。 -
返回 true。
SanitizerAttribute
attribute 在 SanitizerConfig
configuration 上:
注意: 此方法区分两种情况,即我们有全局允许列表还是全局移除列表。如果将 attribute 添加到全局允许列表,可能需要额外操作以修复每元素的允许/移除列表以保持有效性。
-
将 attribute 设为 规范化清洗器属性 的结果,参数为 attribute。
-
如果 configuration["
attributes
"] 存在:-
注释:如果我们有全局允许列表,需要添加 attribute。
-
如果 configuration["
dataAttributes
"] 存在 且 configuration["dataAttributes
"] 为 true 且 attribute 是 自定义数据属性,则返回 false。 -
如果 configuration["
attributes
"] 包含 attribute 则返回 false。 -
注释:修复每元素的允许和移除列表。
-
如果 configuration["
elements
"] 存在:-
遍历 configuration["
elements
"] 中的每个 element:-
如果 element["
attributes
"] 默认 « [] » 包含 attribute:-
移除 attribute 从 element["
attributes
"]。
-
-
断言:element["
removeAttributes
"] 默认 « [] » 不包含 attribute。
-
-
-
追加 attribute 到 configuration["
attributes
"] -
返回 true。
-
-
否则:
-
注释:如果我们有全局移除列表,需要移除 attribute。
-
如果 configuration["
removeAttributes
"] 不包含 attribute:-
返回 false。
-
-
移除 attribute 从 configuration["
removeAttributes
"]。 -
返回 true。
-
SanitizerConfig
configuration 中:
注意: 此方法区分两种情况,即我们有全局允许列表还是全局移除列表。如果将 attribute 添加到全局移除列表,可能需要额外操作以修复每元素的允许/移除列表以保持有效性。如果从全局允许列表中移除 attribute,也可能需要从局部移除列表中移除它。
-
将 attribute 设为 规范化清洗器属性 的结果,参数为 attribute。
-
如果 configuration["
attributes
"] 存在:-
注释:如果我们有全局允许列表,需要添加 attribute。
-
如果 configuration["
attributes
"] 不 包含 attribute:-
返回 false。
-
-
注释:修复每元素的允许和移除列表。
-
如果 configuration["
elements
"] 存在:-
遍历 configuration["
elements
"] 中的每个 element:-
如果 element["
removeAttributes
"] 默认 « [] » 包含 attribute:-
移除 attribute 从 element["
removeAttributes
"]。
-
-
-
-
移除 attribute 从 configuration["
attributes
"]。 -
返回 true。
-
-
否则:
-
注释:如果我们有全局移除列表,需要添加 attribute。
-
如果 configuration["
removeAttributes
"] 包含 attribute 返回 false。 -
注释:修复每元素的允许和移除列表。
-
如果 configuration["
elements
"] 存在:-
遍历 configuration["
elements
"] 中的每个 element:-
如果 element["
attributes
"] 默认 « [] » 包含 attribute:-
移除 attribute 从 element["
attributes
"]。
-
-
如果 element["
removeAttributes
"] 默认 « [] » 包含 attribute:-
移除 attribute 从 element["
removeAttributes
"]。
-
-
-
-
追加 attribute 到 configuration["
removeAttributes
"] -
返回 true。
-
SanitizerConfig
configuration:
SanitizerConfig
configuration:
-
如果 configuration["
attributes
"] 不 存在,则返回 false。 -
如果 configuration["
dataAttributes
"] 等于 allow,则返回 false。 -
如果 allow 为 true:
-
移除所有 attr,从 configuration["
attributes
"] 中移除,其中 attr 是 自定义 data 属性。 -
如果 configuration["
elements
"] 存在:-
遍历 configuration["
elements
"] 中的每个 element:-
如果 element[
attributes
] 存在:-
移除所有 attr,从 element[
attributes
] 中移除,其中 attr 是 自定义 data 属性。
-
-
-
-
-
将 configuration["
dataAttributes
"] 设为 allow。 -
返回 true。
SanitizerConfig
configuration 中执行 remove unsafe,请执行以下步骤:
注意:尽管该算法被称为 remove unsafe,但在本规范中我们严格使用“unsafe”一词来表示在插入文档时会执行 JavaScript 的内容。换言之,此方法将移除可能导致 XSS 的机会。
-
断言:键集合应当与 内置安全基线配置 的键集合相等, 即 «[ "
removeElements
", "removeAttributes
" ] »。 -
令 result 为 false。
-
对于每个 element 在 内置安全基线配置[
removeElements
] 中:-
调用 remove an element 将该 element 从 configuration 中移除。
-
如果调用返回 true,则将 result 设为 true。
-
-
对于每个 attribute 在 内置安全基线配置[
removeAttributes
] 中:-
调用 remove an attribute 将该 attribute 从 configuration 中移除。
-
如果调用返回 true,则将 result 设为 true。
-
-
对于每个 列在 event handler content attributes 中的 attribute:
-
调用 remove an attribute 将该 attribute 从 configuration 中移除。
-
如果调用返回 true,则将 result 设为 true。
-
-
返回 result。
3.3. 设置配置
Sanitizer
sanitizer:
-
使用 规范化 对 configuration 和 allowCommentsAndDataAttributes 进行处理。
-
如果 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:
注意:假设 configuration 是 [WebIDL]
将 JavaScript 值转换为 SanitizerConfig
后的结果。
-
如果 configuration["
elements
"] 和 configuration["removeElements
"] 均不存在,则设置 configuration["removeElements
"] 为 « [] »。 -
如果 configuration["
attributes
"] 和 configuration["removeAttributes
"] 均不存在,则设置 configuration["removeAttributes
"] 为 « [] »。 -
如果 configuration["
elements
"] 存在:-
令 elements 为 « [] »
-
遍历 configuration["
elements
"] 中的每个 element:-
将 规范化带属性的清洗器元素 element 的结果追加到 elements。
-
-
设置 configuration["
elements
"] 为 elements。
-
-
如果 configuration["
removeElements
"] 存在:-
令 elements 为 « [] »
-
遍历 configuration["
removeElements
"] 中的每个 element: -
设置 configuration["
removeElements
"] 为 elements。
-
-
如果 configuration["
replaceWithChildrenElements
"] 存在:-
令 elements 为 « [] »
-
遍历 configuration["
replaceWithChildrenElements
"] 中的每个 element: -
设置 configuration["
replaceWithChildrenElements
"] 为 elements。
-
-
如果 configuration["
attributes
"] 存在:-
令 attributes 为 « [] »
-
遍历 configuration["
attributes
"] 中的每个 attribute: -
设置 configuration["
attributes
"] 为 attributes。
-
-
如果 configuration["
removeAttributes
"] 存在:-
令 attributes 为 « [] »
-
遍历 configuration["
removeAttributes
"] 中的每个 attribute: -
设置 configuration["
removeAttributes
"] 为 attributes。
-
-
如果 configuration["
comments
"] 不存在,则设置 configuration["comments
"] 为 allowCommentsAndDataAttributes。 -
如果 configuration["
attributes
"] 存在 且 configuration["dataAttributes
"] 不存在,则 设置 configuration["dataAttributes
"] 为 allowCommentsAndDataAttributes。
SanitizerElementWithAttributes
element 执行 规范化:
-
令 result 为对 element 调用 canonicalize a sanitizer element 的结果。
-
如果 element 是一个 字典:
-
对于每个 attribute 属于 element["
attributes
"]:-
追加 规范化清理器属性 attribute 的结果到 result["
attributes
"]。
-
-
对于每个 attribute 属于 element["
removeAttributes
"]:-
追加 规范化清理器属性 attribute 的结果到 result["
removeAttributes
"]。
-
-
-
返回 result。
SanitizerElement
element 执行 规范化,返回用 element 和默认命名空间为 HTML 命名空间 调用 canonicalize a sanitizer name 的结果。
SanitizerAttribute
attribute 执行 规范化,返回用 attribute 和默认命名空间为 null 调用 canonicalize a sanitizer name 的结果。
3.5. 辅助算法
对于本规范中使用的 规范化后的
元素
与 属性名
列表,其成员资格基于同时匹配 "name
" 与 "namespace
" 条目:
3.6. 内置项
有四个内置项:
内置安全默认配置 如下:
{ "elements" : [ { "name" : "html" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "head" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "title" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "body" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "article" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "section" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "nav" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "aside" , "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" : "hgroup" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "header" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "footer" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "address" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "p" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "hr" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "pre" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "blockquote" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "cite" , "namespace" : null } ] }, { "name" : "ol" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "reversed" , "namespace" : null }, { "name" : "start" , "namespace" : null }, { "name" : "type" , "namespace" : null } ] }, { "name" : "ul" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "menu" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "li" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "value" , "namespace" : null } ] }, { "name" : "dl" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "dt" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "dd" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "figure" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "figcaption" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "main" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "search" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "div" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "a" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "href" , "namespace" : null }, { "name" : "rel" , "namespace" : null }, { "name" : "hreflang" , "namespace" : null }, { "name" : "type" , "namespace" : null } ] }, { "name" : "em" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "strong" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "small" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "s" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "cite" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "q" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "dfn" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "abbr" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "ruby" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "rt" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "rp" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "data" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "value" , "namespace" : null } ] }, { "name" : "time" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "datetime" , "namespace" : null } ] }, { "name" : "code" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "var" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "samp" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "kbd" , "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" : "i" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "b" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "u" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "mark" , "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" : "span" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "br" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "wbr" , "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" : "del" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "cite" , "namespace" : null }, { "name" : "datetime" , "namespace" : null } ] }, { "name" : "table" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "caption" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "colgroup" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "span" , "namespace" : null } ] }, { "name" : "col" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "span" , "namespace" : null } ] }, { "name" : "tbody" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "thead" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "tfoot" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "tr" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "td" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "colspan" , "namespace" : null }, { "name" : "rowspan" , "namespace" : null }, { "name" : "headers" , "namespace" : null } ] }, { "name" : "th" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "colspan" , "namespace" : null }, { "name" : "rowspan" , "namespace" : null }, { "name" : "headers" , "namespace" : null }, { "name" : "scope" , "namespace" : null }, { "name" : "abbr" , "namespace" : null } ] }, { "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" : "form" , "namespace" : null }, { "name" : "fence" , "namespace" : null }, { "name" : "separator" , "namespace" : null }, { "name" : "lspace" , "namespace" : null }, { "name" : "rspace" , "namespace" : null }, { "name" : "stretchy" , "namespace" : null }, { "name" : "symmetric" , "namespace" : null }, { "name" : "maxsize" , "namespace" : null }, { "name" : "minsize" , "namespace" : null }, { "name" : "largeop" , "namespace" : null }, { "name" : "movablelimits" , "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" : "width" , "namespace" : null }, { "name" : "height" , "namespace" : null }, { "name" : "depth" , "namespace" : null }, { "name" : "lspace" , "namespace" : null }, { "name" : "voffset" , "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" : "width" , "namespace" : null }, { "name" : "height" , "namespace" : null }, { "name" : "depth" , "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" : "svg" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "viewBox" , "namespace" : null }, { "name" : "preserveAspectRatio" , "namespace" : null }, { "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" : "defs" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [] }, { "name" : "title" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [] }, { "name" : "desc" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [] }, { "name" : "metadata" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [] }, { "name" : "path" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "pathLength" , "namespace" : null }, { "name" : "d" , "namespace" : null } ] }, { "name" : "rect" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "pathLength" , "namespace" : null }, { "name" : "x" , "namespace" : null }, { "name" : "y" , "namespace" : null }, { "name" : "width" , "namespace" : null }, { "name" : "height" , "namespace" : null }, { "name" : "rx" , "namespace" : null }, { "name" : "ry" , "namespace" : null } ] }, { "name" : "circle" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "pathLength" , "namespace" : null }, { "name" : "cx" , "namespace" : null }, { "name" : "cy" , "namespace" : null }, { "name" : "r" , "namespace" : null } ] }, { "name" : "ellipse" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "pathLength" , "namespace" : null }, { "name" : "cx" , "namespace" : null }, { "name" : "cy" , "namespace" : null }, { "name" : "rx" , "namespace" : null }, { "name" : "ry" , "namespace" : null } ] }, { "name" : "line" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "pathLength" , "namespace" : null }, { "name" : "x1" , "namespace" : null }, { "name" : "y1" , "namespace" : null }, { "name" : "x2" , "namespace" : null }, { "name" : "y2" , "namespace" : null } ] }, { "name" : "polyline" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "pathLength" , "namespace" : null }, { "name" : "points" , "namespace" : null } ] }, { "name" : "polygon" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "pathLength" , "namespace" : null }, { "name" : "points" , "namespace" : null } ] }, { "name" : "text" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "lengthAdjust" , "namespace" : null }, { "name" : "x" , "namespace" : null }, { "name" : "y" , "namespace" : null }, { "name" : "dx" , "namespace" : null }, { "name" : "dy" , "namespace" : null }, { "name" : "rotate" , "namespace" : null }, { "name" : "textLength" , "namespace" : null } ] }, { "name" : "tspan" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "lengthAdjust" , "namespace" : null }, { "name" : "x" , "namespace" : null }, { "name" : "y" , "namespace" : null }, { "name" : "dx" , "namespace" : null }, { "name" : "dy" , "namespace" : null }, { "name" : "rotate" , "namespace" : null }, { "name" : "textLength" , "namespace" : null } ] }, { "name" : "textPath" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "lengthAdjust" , "namespace" : null }, { "name" : "textLength" , "namespace" : null }, { "name" : "path" , "namespace" : null }, { "name" : "startOffset" , "namespace" : null }, { "name" : "method" , "namespace" : null }, { "name" : "spacing" , "namespace" : null }, { "name" : "side" , "namespace" : null } ] }, { "name" : "foreignObject" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "x" , "namespace" : null }, { "name" : "y" , "namespace" : null }, { "name" : "width" , "namespace" : null }, { "name" : "height" , "namespace" : null } ] }, { "name" : "marker" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "viewBox" , "namespace" : null }, { "name" : "preserveAspectRatio" , "namespace" : null }, { "name" : "refX" , "namespace" : null }, { "name" : "refY" , "namespace" : null }, { "name" : "markerUnits" , "namespace" : null }, { "name" : "markerWidth" , "namespace" : null }, { "name" : "markerHeight" , "namespace" : null }, { "name" : "orient" , "namespace" : null } ] } ], "attributes" : [ { "name" : "dir" , "namespace" : null }, { "name" : "lang" , "namespace" : null }, { "name" : "title" , "namespace" : null }, { "name" : "displaystyle" , "namespace" : null }, { "name" : "mathbackground" , "namespace" : null }, { "name" : "mathcolor" , "namespace" : null }, { "name" : "mathsize" , "namespace" : null }, { "name" : "scriptlevel" , "namespace" : null }, { "name" : "fill" , "namespace" : null }, { "name" : "transform" , "namespace" : null }, { "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" : "direction" , "namespace" : null }, { "name" : "display" , "namespace" : null }, { "name" : "dominant-baseline" , "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" : "letter-spacing" , "namespace" : null }, { "name" : "marker-end" , "namespace" : null }, { "name" : "marker-mid" , "namespace" : null }, { "name" : "marker-start" , "namespace" : null }, { "name" : "opacity" , "namespace" : null }, { "name" : "paint-order" , "namespace" : null }, { "name" : "pointer-events" , "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" : "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" : "script" }, { "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" : "embed" }, { "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" : "script" }, { "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" : "embed" }, { "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 属性覆盖攻击(DOM clobbering)
本节非规范性内容。
DOM clobbering 描述了一种攻击方式:恶意 HTML 通过 id
或 name
属性命名元素,使 HTML 元素在 DOM 中的某些属性(如
children
)被恶意内容覆盖,导致应用混乱。
Sanitizer API 默认状态下无法防护 DOM clobbering 攻击,但可以通过配置移除 id
和 name
属性。
4.3. 利用脚本装置进行 XSS 攻击
本节非规范性内容。
脚本装置(Script gadgets)是一种攻击技术,攻击者利用流行 JavaScript 库的现有应用代码,使自己的代码得以执行。通常是通过注入看似无害的代码或 DOM 节点,这些内容会被某些框架解析并导致实际执行 JavaScript。
Sanitizer API 无法防止此类攻击,页面作者需自行决定是否允许未知元素,并且需明确配置允许哪些未知属性、元素,以及哪些被广泛用于模板和框架的标记,如 data-
和
slot
属性、<slot>
和 <template>
元素等。我们认为这些限制并非穷尽,鼓励页面作者对第三方库的行为进行排查。
4.4. 变异型 XSS (mXSS)
本节非规范性内容。
变异型 XSS(mXSS)描述了基于解析器上下文不一致的攻击。例如,在没有正确上下文的情况下解析 HTML 片段,导致序列化为字符串后再次插入不同父元素时解析行为发生变化。攻击者可利用外来内容或标签嵌套错误来实现此类攻击。
Sanitizer API 只提供将字符串转为节点树的功能。所有净化函数隐式提供上下文:Element.setHTML()
使用当前元素,Document.parseHTML()
创建新文档。因此,Sanitizer API 不直接受变异型 XSS 影响。
如果开发者将净化后的节点树转为字符串(如通过 .innerHTML
),再进行解析,则可能发生变异型 XSS。我们不推荐此做法。如确需处理或传递 HTML
字符串,则该字符串应被视为不可信,在插入 DOM 时应再次净化。换言之,净化并序列化后的 HTML 树不再被视为已净化。
关于 mXSS 的更完整讨论可见 [MXSS]。
5. 致谢
本项目参考并受到 [DOMPURIFY](cure53)、Internet Explorer 的 window.toStaticHTML()
以及 Ben Bucksch 的 [HTMLSanitizer] 的启发。
感谢 Anne van Kesteren、Krzysztof Kotowicz、Tom Schuster、Luke Warlow、Guillaume Weghsteen 和 Mike West 的宝贵反馈。