1. 介绍
本节为非规范性内容。
Web 应用程序通常需要在客户端处理 HTML 字符串,例如作为客户端模板解决方案的一部分,或者用于渲染用户生成的内容等。然而,以安全的方式处理这些字符串是困难的。将字符串拼接在一起并直接插入到 Element
的
innerHTML
中的简单方法存在风险,因为它可能以多种意想不到的方式导致 JavaScript 执行。
像 [DOMPURIFY] 这样的库试图通过在插入之前仔细解析和清理字符串来解决这个问题,它们通过构建 DOM 并使用允许列表过滤其成员。然而,这种方法被证明是脆弱的,因为暴露给 Web 的解析 API 并不总是以合理的方式映射到浏览器在实际 DOM 中渲染字符串时的行为。此外,这些库需要随着时间的推移跟上浏览器行为的变化;曾经安全的内容可能会因为新的平台级功能而变成定时炸弹。
浏览器对何时执行代码有相当好的判断。我们可以通过教浏览器如何以安全的方式从任意字符串渲染 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 为调用 Get Trusted Type compliant string 算法的结果,参数为
TrustedHTML
、 this 的 relevant global object、html、“Element setHTMLUnsafe” 和 “script”。 -
如果 this 是一个
template
元素,则令 target 为 this 的 template contents;否则为 this。 -
给定 target、this、 compliantHTML、options 和 false,设置并过滤 HTML。
Element
的
setHTML(html, options) 方法步骤如下:
-
如果 this 是一个
template
,则令 target 为 this 的 template contents; 否则为 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 为调用 Get Trusted Type compliant string 算法的结果,参数为
TrustedHTML
, this 的 relevant global object, html, "ShadowRoot setHTMLUnsafe", 和 "script". -
使用 this、this 的 shadow host (作为上下文元素)、 compliantHTML、options 和 false,设置并过滤 HTML。
ShadowRoot
的
setHTML(html, options) 方法步骤如下:
-
使用 this (作为目标)、this (作为上下文元素)、 html、options 和 true,设置并过滤 HTML。
Document
接口新增了两个用于解析整个 Document
的方法:
partial interface Document {static Document ((
parseHTMLUnsafe TrustedHTML or DOMString ),
html optional SetHTMLUnsafeOptions = {});
options static Document (
parseHTML DOMString ,
html optional SetHTMLOptions = {}); };
options
-
令 compliantHTML 为调用 Get Trusted Type compliant string 算法的结果,参数为
TrustedHTML
、 this 的 relevant 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 和 false 在 document 上调用 sanitize。
-
返回 document。
-
令 document 为一个新的
Document
, 其 content type 为 "text/html"。注意:由于 document 没有浏览上下文,因此脚本被禁用。
-
将 document 的 allow declarative shadow roots 设置为 true。
-
给定 document 和 html,从字符串解析 HTML。
-
令 sanitizer 为使用 options 和 true 调用 从选项获取清理器实例的结果。
-
使用 sanitizer 和 true 在 document 上调用 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"); // 查询配置:
configuration SanitizerConfig (); // 修改 Sanitizer 的列表和字段:
get undefined (
allowElement SanitizerElementWithAttributes );
element undefined (
removeElement SanitizerElement );
element undefined (
replaceElementWithChildren SanitizerElement );
element undefined (
allowAttribute SanitizerAttribute );
attribute undefined (
removeAttribute SanitizerAttribute );
attribute undefined (
setComments boolean );
allow undefined (
setDataAttributes boolean ); // 移除执行脚本的标记。可能修改多个列表:
allow undefined (); };
removeUnsafe
一个 Sanitizer
有一个关联的 配置,它是一个 SanitizerConfig
。
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
3. 算法
Element
或 DocumentFragment
target,一个 Element
contextElement,一个字符串 html,一个字典
options,以及一个布尔值 safe:
-
如果 safe 为真且 contextElement 的本地名称是 "
script
" 且 contextElement 的命名空间是HTML 命名空间或SVG 命名空间,则返回。 -
令 sanitizer 为使用 options 和 safe 调用从选项获取 Sanitizer 实例的结果。
-
令 newChildren 为给定 contextElement、html 和 true 的HTML 片段解析算法的结果。
-
令 fragment 为一个新的
DocumentFragment
, 其节点文档是 contextElement 的节点文档。 -
使用 sanitizer 和 safe 在 fragment 上运行净化。
-
在 target 内用 fragment 替换所有内容。
注意:此算法同时适用于 SetHTMLOptions
和 SetHTMLUnsafeOptions
。
它们仅在默认值上有所不同。
3.1. 净化算法
ParentNode
node,一个 Sanitizer
sanitizer,以及一个 布尔值 safe,运行以下步骤:
-
令 configuration 为 sanitizer 的 配置 的值。
-
如果 safe 为 true,则将 configuration 设置为在 configuration 上调用 移除不安全内容 的结果。
-
在 node、configuration 上调用 核心净化,并将 handleJavascriptNavigationUrls 设置为 safe。
ParentNode
node,一个 SanitizerConfig
configuration,以及一个 布尔值 handleJavascriptNavigationUrls,从 node
开始遍历 DOM 树,并可能递归处理一些特殊情况(例如模板内容)。它包含以下步骤:
-
-
断言:child 实现了
Text
、Comment
、Element
, 或DocumentType
。注意:目前,此算法仅在 HTML 解析器的输出上调用,该断言应成立。
DocumentType
应仅出现在parseHTML
和parseHTMLUnsafe
中。 如果将来此算法将用于不同的上下文中,则需要重新检查此假设。 -
如果 child 实现了
DocumentType
, 则 继续。 -
否则:
-
令 elementName 为一个
SanitizerElementNamespace
, 其包含 child 的 本地名称 和 命名空间。 -
如果 configuration["
replaceWithChildrenElements
"] 包含 elementName: -
如果 configuration["
removeElements
"] 包含 elementName, 或者如果 configuration["elements
"] 不 为空 且不 包含 elementName: -
如果 elementName 等于 «[ "
name
" → "template
", "namespace
" → HTML 命名空间 ]» -
如果 child 是一个 影子宿主, 则在 child 的 影子根 上使用 configuration 和 handleJavascriptNavigationUrls 调用 核心净化。
-
对于 child 的 属性列表 中的每个 attribute:
-
令 attrName 为一个
SanitizerAttributeNamespace
, 其包含 attribute 的 本地名称 和 命名空间。 -
如果 configuration["
removeAttributes
"] 包含 attrName, 则从 child 中移除 attribute。 -
如果 configuration["
elements
"]["removeAttributes
"] 包含 attrName,则从 child 中移除 attribute。 -
如果以下所有条件均为 false,则从 child 中移除 attribute。
-
configuration["
attributes
"] 存在 且 包含 attrName -
configuration["
elements
"]["attributes
"] 包含 attrName -
"data-" 是 本地名称 的 代码单元前缀 且 命名空间 为
null
且 configuration["dataAttributes
"] 为 true
-
-
如果 handleJavascriptNavigationUrls 为真:
-
如果 «[elementName, attrName]» 匹配 内置导航 URL 属性列表 中的一个条目,并且如果 attribute 包含 javascript: URL,则从 child 中移除 attribute。
-
如果 child 的 命名空间 是 MathML 命名空间 且 attr 的 本地名称 是 "
href
" 且 attr 的 命名空间 为null
或 XLink 命名空间 且 attr 包含 javascript: URL, 则 移除 attr。 -
如果 内置动画 URL 属性列表 包含 «[elementName, attrName]» 且 attr 的 值 是 "
href
" 或 "xlink:href
",则 移除 attr。
-
-
-
在 child 上使用 configuration 和 handleJavascriptNavigationUrls 调用 核心净化。
-
-
javascript:
URL。由于导航本身不是 XSS 威胁,我们处理到
javascript:
URL 的导航,但不处理一般的导航。
声明式导航可分为以下几类:
前两种情况由 内置导航 URL 属性列表 涵盖。
MathML 的情况由一个单独的规则涵盖,因为本规范中没有形式化的方法来涵盖“每个命名空间的全局”规则。
SVG 动画的情况由 内置动画 URL 属性列表 涵盖。但是由于 SVG
动画元素的解释取决于动画目标,并且在净化期间我们无法知道最终目标是什么,因此 净化 算法会阻止 href
属性的任何动画。
-
令 url 为在 attribute 的 值 上运行 基本 URL 解析器 的结果。
-
如果 url 是
failure
,则返回 false。
3.2. 配置处理
SanitizerConfig
configuration 允许一个元素 element,执行:
-
将 element 设置为使用 element 规范化带属性的净化器元素的结果。
-
从 configuration["
removeElements
"] 中移除 element。 -
从 configuration["
replaceWithChildrenElements
"] 中移除 element。
注意:allowElement 的处理比其他方法要复杂一些,因为元素允许列表可以有每个元素的允许和移除属性列表。我们首先从列表中移除给定的元素,然后再添加它,这样做的效果是重置(而不是合并或以其他方式修改)每个元素的列表为传入的内容。换句话说,每个元素的允许和移除列表只能整体设置。
注意:移除操作会匹配名称和命名空间,因此添加带属性的元素仍会从
removeElements
和 replaceWithChildrenElements
列表中移除匹配的元素。
SanitizerConfig
configuration 中移除一个元素 element,执行:
-
将 element 设置为使用 element 规范化净化器元素的结果。
-
将 element 添加到 configuration["
removeElements
"]。 -
从 configuration["
replaceWithChildrenElements
"] 中移除 element。
SanitizerConfig
configuration 中用其子元素替换一个元素
element,执行:
-
将 element 设置为使用 element 规范化净化器元素的结果。
-
将 element 添加到 configuration["
replaceWithChildrenElements
"]。 -
从 configuration["
removeElements
"] 中移除 element。
SanitizerConfig
configuration 上允许一个属性 attribute,执行:
-
将 attribute 设置为使用 attribute 规范化净化器属性的结果。
-
将 attribute 添加到 configuration["
attributes
"]。 -
从 configuration["
removeAttributes
"] 中移除 attribute。
SanitizerConfig
configuration 中移除一个属性 attribute,执行:
-
将 attribute 设置为使用 attribute 规范化净化器属性的结果。
-
将 attribute 添加到 configuration["
removeAttributes
"]。 -
从 configuration["
attributes
"] 中移除 attribute。
SanitizerConfig
configuration 上使用 allow 设置注释,执行:
-
将 configuration["
comments
"] 设置为 allow。
SanitizerConfig
configuration 上使用 allow 设置数据属性,执行:
-
将 configuration["
dataAttributes
"] 设置为 allow。
注意:虽然此算法称为移除不安全内容,但我们严格按照本规范的意义使用术语“不安全”,以表示在插入文档时将执行 JavaScript 的内容。换句话说,此方法将消除 XSS 的机会。
要从 configuration 中移除不安全内容,执行以下操作:
-
断言:内置安全基线配置具有已设置的
removeElements
和removeAttributes
键,但未设置elements
、replaceWithChildrenElements
或attributes
。 -
令 result 为 configuration 的副本。
-
对于内置安全基线配置[
removeElements
] 中的每个 element:-
使用 element 和 result 调用移除元素。
-
-
对于内置安全基线配置[
removeAttributes
] 中的每个 attribute:-
使用 attribute 和 result 调用移除属性。
-
-
对于事件处理程序内容属性中列出的每个 attribute:
-
使用 attribute 和 result 调用移除属性。
-
-
返回 result。
Sanitizer
sanitizer:
-
对于 configuration["
removeElements
"] 中的每个 element,执行: -
对于 configuration["
replaceWithChildrenElements
"] 中的每个 element,执行: -
对于 configuration["
attributes
"] 中的每个 attribute,执行: -
对于 configuration["
removeAttributes
"] 中的每个 attribute,执行: -
如果 configuration["
dataAttributes
"] 存在:-
则使用 configuration["
dataAttributes
"] 和 sanitizer 的 配置调用设置数据属性。 -
否则使用 allowCommentsAndDataAttributes 和 sanitizer 的 配置调用设置数据属性。
-
-
返回以下所有条件是否都为真:
-
configuration["
elements
"] 的大小等于 sanitizer 的 配置["elements
"] 的大小。 -
configuration["
removeElements
"] 的大小等于 sanitizer 的 配置["removeElements
"] 的大小。 -
configuration["
replaceWithChildrenElements
"] 的大小等于 sanitizer 的 配置["replaceWithChildrenElements
"] 的大小。 -
configuration["
attributes
"] 的大小等于 sanitizer 的 配置["attributes
"] 的大小。 -
configuration["
removeAttributes
"] 的大小等于 sanitizer 的 配置["removeAttributes
"] 的大小。 -
configuration["
elements
"] 或 configuration["removeElements
"] 存在, 或者两者都不存在,但不能同时存在。 -
configuration["
attributes
"] 或 configuration["removeAttributes
"] 存在, 或者两者都不存在,但不能同时存在。
-
注意:本规范的先前版本对如何规范化配置有详细的定义。现在这实际上已移至方法定义中。
注意:此操作是根据 Sanitizer
上的操作方法定义的。这些方法会从其他列表中移除匹配的条目。最后一步中的大小相等性检查会捕获到这一点。例如:{ allow: ["div", "div"] }
会创建一个在允许列表中包含一个元素的 Sanitizer。最终测试将返回 false,这将导致调用者抛出异常。
SanitizerElementWithAttributes
element,执行以下操作:
-
令 result 为使用 element 规范化净化器元素的结果。
-
如果 element 是一个字典:
-
对于 element["
attributes
"] 中的每个 attribute:-
将使用 attribute 规范化净化器属性的结果添加到 result["
attributes
"]。
-
-
对于 element["
removeAttributes
"] 中的每个 attribute:-
将使用 attribute 规范化净化器属性的结果添加到 result["
removeAttributes
"]。
-
-
-
返回 result。
SanitizerAttribute
attribute,
返回使用 attribute 和作为默认命名空间的 null 规范化净化器名称的结果。
3.3. 支持算法
对于本规范中使用的规范化的 元素
和 属性名称
列表,列表成员资格基于匹配 "name
" 和
"namespace
"
条目:
3.4. 内置项
有四个内置项:
-
内置安全基线配置,以及
内置安全默认配置如下:
{ "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" : [] }
警告:移除不安全内容算法规定额外移除在 [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 的跨站脚本攻击。指定的 API 不得支持构建会留下可执行脚本标记的 Sanitizer 对象,这样做将是威胁模型中的一个错误。
话虽如此,正确使用 Sanitizer API 仍无法防范某些安全问题,以下各节将列出这些场景。
4.1. 服务器端反射型和存储型 XSS
本节不具有规范性。
Sanitizer API 仅在 DOM 中运行,并增加了遍历和过滤现有 DocumentFragment 的功能。Sanitizer 无法解决服务器端反射型或存储型 XSS。
4.2. DOM Clobbering
本节不具有规范性。
DOM Clobbering 描述了一种攻击,其中恶意 HTML 通过使用 id
或 name
属性命名元素来混淆应用程序,从而使 DOM 中 HTML 元素的
children
等属性被恶意内容覆盖。
Sanitizer API 在其默认状态下无法防止 DOM Clobbering 攻击,但可以配置为移除 id
和 name
属性。
4.3. 使用脚本小工具的 XSS
本节不具有规范性。
脚本小工具是一种技术,攻击者利用流行 JavaScript 库中的现有应用程序代码来执行自己的代码。这通常通过注入看起来无害的代码或看似惰性的 DOM 节点来完成,这些节点仅由框架解析和解释,然后框架根据该输入执行 JavaScript。
Sanitizer API 无法阻止这些攻击,但要求页面作者通常明确允许未知元素,并且作者还必须明确配置已知广泛用于模板和特定于框架的代码的未知属性、元素和标记,例如 data-
和
slot
属性以及诸如 <slot>
和 <template>
之类的元素。我们认为这些限制并非详尽无遗,并鼓励页面作者检查其第三方库是否存在此行为。
4.4. 突变型 XSS
本节不具有规范性。
突变型 XSS 或 mXSS 描述了一种基于在没有正确上下文的情况下解析 HTML 片段时解析器上下文不匹配的攻击。特别是,当已解析的 HTML 片段已序列化为字符串时,不能保证在插入到不同的父元素时该字符串会被完全相同地解析和解释。执行此类攻击的一个示例是依赖于外部内容或错误嵌套标签的解析行为的更改。
Sanitizer API 仅提供将字符串转换为节点树的函数。所有净化器函数都隐式提供上下文: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、Tom Schuster、Luke Warlow、
Guillaume Weghsteen 和 Mike West 提出的宝贵反馈意见。