1. 引言
本节不是规范性的。
内容安全策略是抵御跨站脚本攻击的一种出色防御手段,它允许
开发者加固自己的站点,以防恶意脚本、样式以及其他
资源类型的注入。不过,它并不能让开发者将限制应用到通过
iframe
加载进来的第三方内容。
允许 CSP 直接应用于这些第三方
上下文会很危险;CSP 对资源加载提供了相当细粒度的控制,并且很
可能通过拒绝某个本来安全的页面访问
特定脚本,而把漏洞引入该页面。我们在过去的特性中见过这类问题,例如
X-XSS-Protection,
因此必须小心,避免以新的形式重新引入这些问题。
尽管如此,能够对小组件、广告 以及其他类型的第三方内容施加限制会非常有用。本文档提出了一种机制,该机制依赖于 被嵌入内容的显式选择加入,这应当使小组件能够 与其嵌入方协作,协商出一组合理的限制。
简而言之,嵌入方通过在
iframe
元素上设置一个属性来提出一项内容安全策略。该策略会随被框入内容的 HTTP 请求一起,在 HTTP
请求标头
(`Sec-Required-CSP`)
中传输。
如果被嵌入内容可以接受该策略,它可以
通过在
`Content-Security-Policy`
或
`Allow-CSP-From`
标头中连同
响应返回它来强制执行该策略。
如果响应包含的策略至少与嵌入方请求的策略一样严格, 或接受嵌入方提供的策略,则用户代理将渲染被嵌入内容。如果 不存在这样的断言,响应将被阻止。
1.1. 示例
iframe
元素,并为其设置
csp
属性来包含广告:
<iframe src="https://advertisements-r-us.example.com/ad1.cfm"
csp="script-src https://trusted-cdn.example.com/">
</iframe>
这将生成一个发送到 advertisements-r-us.example.com 的请求,该请求带有
`Sec-Required-CSP`
标头,如下所示:
GET / HTTP/1.1 Host: advertisements-r-us.example.com ... Sec-Required-CSP: script-src https://trusted-cdn.example.com/ ...
广告服务器解析此请求标头,判断它可以接受,并向响应添加一个
标头,通知用户代理它将遵守其嵌入方(https://example.com)施加的限制:
HTTP/1.1 200 OK ... Allow-CSP-From: https://example.com
Content-Security-Policy`
标头来接受这些限制,该标头中的策略至少与
嵌入方要求的策略一样强。例如,它可能希望确保无论
嵌入方允许什么,都不会加载任何插件。它可以通过发出一个包含嵌入方
限制、并在其基础上添加更多限制的策略来做到这一点:
HTTP/1.1 200 OK ... Content-Security-Policy: script-src https://trusted-cdn.example.com/; object-src 'none'
由于响应所断言的策略允许的请求严格少于请求所要求的策略, 该框架会成功加载。
请注意,服务器也可以交付两个策略,一个精确镜像 嵌入方的限制,另一个进一步收紧这些限制:
HTTP/1.1 200 OK ... Content-Security-Policy: script-src https://trusted-cdn.example.com/, object-src 'none'
`Content-Security-Policy`
标头值中的 "," 会将字符串拆分为两个
序列化策略,每个策略都会被强制执行。用户代理验证随响应
交付的某个策略与要求相匹配,并且由于额外的策略只会使
页面上的有效策略更加严格,因此允许框架
成功加载。
2. 框架
在较高层面上,本文档描述了一种机制,通过该机制,被嵌入方可以选择加入由其嵌入方 指定的一组限制。该机制涉及几个步骤:
-
嵌入方通过
csp属性,在iframe元素上指定一个必需策略。这在 § 2.1 <iframe> 的 csp 属性中有更详细的描述。 -
该属性的值将作为 `
Sec-Required-CSP` 请求标头,随任何目标为iframe的 子可导航对象的 导航请求一起发送。此标头在 § 2.2 Sec-Required-CSP HTTP 请求标头中有更详细的描述。 -
服务器可以检查 `
Sec-Required-CSP` 标头,以确定它是否希望 接受所需策略。如果是,它可以通过在响应中发送一个 `Content-Security-Policy` 标头来隐式选择加入,该标头包含至少与 所需策略一样强的策略;或者通过在 响应中发送 `Allow-CSP-From` 标头来显式选择加入,从而允许嵌入源设置它希望的任何策略。显式 机制很直接,描述见 § 2.3 Allow-CSP-From HTTP 响应标头。隐式机制 相当复杂,并构成了整个 § 3 隐式策略 接受节。如果服务器不希望接受所需策略,它可以返回一个显式错误,或者 简单地返回通常的数据,而不带有匹配的 `
Content-Security-Policy` 标头或 `Allow-CSP-From` 标头。在这种情况下,用户代理将阻止该响应。此 与 HTML 的 navigate 算法的集成描述见 § 2.4 与 HTML 集成,并且 阻止机制在 § 4.1 response 对 request 是否被 requiredCSP 阻止?中详细说明。
2.1. <iframe> 的 csp 属性
iframe
元素具有一个 csp 属性,它指定
被嵌入文档必须同意对自身强制执行的
策略。例如,以下
HTML 会加载 https://embedee.example.com/,并确保
object-src 'none' 对其强制执行:
<iframe src="https://embedee.example.com/" csp="object-src 'none'"> </iframe>
对于给定
元素(element)的
csp
属性,如果以下所有陈述均为
真,则一个字符串
(value)是其有效属性值:
-
value 不是空字符串。
-
value 的长度小于或等于 4096。
注:我们对
csp属性的值强制执行最大长度,以保护服务器 免受可能令人困惑的输入影响。参见下文 § 5.4 标头长度。 -
value 匹配 [CSP] 中定义的 serialized-policy ABNF 语法。
-
以下陈述之一为真:
-
element 的节点文档的策略容器的required CSP 为
null。 -
将 value 作为 "
enforce" 解析的结果,被 element 的节点文档的策略容器的 required CSP 包含。
-
csp
属性的有效值,因为它们是有效的 CSP
语法:
-
script-src 'none' -
script-src 'self'; object-src 'none'; sandbox -
not-a-directive https://whatever.not-a-tld
注:我们认为最后一项有效,即使 它并未表达有意义的策略, 这是为了保持与未来 CSP 语法的前向兼容性。
另一方面,以下内容不匹配 CSP 语法,因此不会被视为有效的 属性值:
-
script-src *\nInjected-Header: XSS! -
💩
注:我们需要谨慎处理
csp
属性中允许的值,因为其
内容最终会作为 HTTP 请求标头被反射出去。这个问题在 中有更详细的讨论。
iframe
的
csp
属性有一个对应的 IDL 属性,由以下 WebIDL 语法定义
[WEBIDL]:
partial interface HTMLIFrameElement { [CEReactions ]attribute DOMString ; };csp
2.2. Sec-Required-CSP HTTP 请求标头
为了确保被嵌入资源可以决定是否愿意遵守
嵌入方的要求,在
iframe
的
csp
属性中表达的策略,会通过
"Sec-Required-CSP" HTTP 请求标头
随受影响的导航
请求一起传递。该标头的值由以下 ABNF 表示
[RFC5234]:
Sec-Required-CSP = serialized-policy
用户代理不得发送超过一个名为
"Sec-Required-CSP" 的 HTTP 响应标头字段,
且任何这样的标头不得包含超过一个 serialized-policy。
服务器必须只处理接收到的第一个此类标头中的第一项策略。如
中所讨论,服务器还应仔细考虑简单地
将策略反射回客户端的影响。如果服务器希望简单地接受嵌入方的
要求,则
`Allow-CSP-From`
标头是更安全的选择。
此标头作为 HTML 的 navigate 算法的一部分设置(参见 § 2.4 与 HTML 集成,了解调用以下算法的钩子的详细信息):
Sec-Required-CSP
标头,运行以下
步骤:
-
如果 request 不是导航请求,则返回。
-
如果 requirement 为
null,则返回。 -
断言:requirement 是一个序列化 CSP,匹配 [CSP] 中定义的 serialized-policy 语法。
-
将名为 "`
Sec-Required-CSP`", 值为 requirement 的标头 追加到 request 的标头列表。
2.3.
Allow-CSP-From HTTP 响应标头
被嵌入方可以通过响应
"Allow-CSP-From" HTTP 响应标头,选择加入接受由嵌入方指定的策略。该
标头的值由以下 ABNF 表示 [RFC5234]:
Allow-CSP-From = origin-or-null / wildcard
2.4. 与 HTML 集成
-
iframe元素具有csp属性,定义见 § 2.1 <iframe> 的 csp 属性。 -
向策略容器 结构体添加一个 required CSP 项,该项是一个 序列化 CSP 或 null,且初始为 null。
-
更新 HTML 的快照化目标快照参数 算法,将新的 required CSP 项设置为给定 targetNavigable 时确定 required CSP 的结果。
-
更新 navigate 算法,将步骤 18.7.7 中创建的 导航参数 结构体的 required CSP 设置为 targetSnapshotParams 的required CSP。
-
在 request 创建之后,向 HTML 的通过获取创建导航参数 算法添加以下步骤:
-
为 request 和 targetSnapshotParams 的required CSP 设置 Sec-Required-CSP 标头。
-
-
更新通过获取创建导航参数,以将返回的 导航参数的required CSP 设置为 targetSnapshotParams 的 required CSP。
-
更新从 srcdoc 资源创建导航参数,以将返回的导航参数的 required CSP 设置为 targetSnapshotParams 的required CSP。
-
向 HTML 的从获取响应创建策略容器算法添加一个可选的 requiredCSP(一个序列化 CSP 或 null,默认为 null)作为 参数,并添加以下步骤:
-
如果 requiredCSP 不为 null:
-
令 required policy 为将 requiredCSP 作为 "
enforce" 解析的结果。 -
令 response policies 为解析 response 的内容安全策略的结果。
-
如果在 required policy、response 的url 的源、response policies 以及 response 的url 的源上执行 § 3.2.1 CSP 列表包含时返回 "
Does Not Subsume":注:如果响应的 策略已经包含所需策略,我们不会将 所需策略追加到策略容器。这确保了如果嵌入方 要求 一个特定 nonce(例如
nonce-abc),而被嵌入方提供自己的 兼容 nonce (例如nonce-xyz),用户代理只会强制执行被嵌入方的 nonce。 如果我们 同时追加两者,则该文档实际上会阻止所有脚本,因为没有脚本 能同时 满足两个 nonce。 -
将 result 的required CSP 设置为 requiredCSP。
-
-
-
更新通过获取创建导航参数,以将 targetSnapshotParams 的required CSP 传递给 从获取响应创建策略容器。
-
向 HTML 的 navigate 算法中会导致 导航被阻止的条件列表添加以下内容(例如,在
X-Frame-Options检查之后):-
当在
navigationParams的response、navigationParams的 request 以及navigationParams的 required CSP 上执行 § 4.1 response 对 request 是否被 requiredCSP 阻止? 算法时,该算法返回 "Blocked"。
-
2.4.1. Required CSP
3. 隐式策略接受
被嵌入方可以通过在响应中一并返回
`Allow-CSP-From`
标头,来显式接受其嵌入方指定的策略要求。该要求也可以
通过交付一个
`Content-Security-Policy`
标头来隐式接受,该标头包含一个策略(或一组策略),
其净效果至少与嵌入方要求的策略一样严格。
不过,“至少一样严格”并不是非常精确。简单情况很直接:如果
嵌入方要求 object-src https://cdn.example.com,被嵌入方可以用
object-src 'none' 响应。由于前者会阻止的每一种可能资源
也会被后者阻止
(因为它完全不允许任何对象),我们不会阻止该嵌入。CSP 的
语法复杂性使得在更复杂的
情况下进行推理有些困难。例如,给定 script-src 'unsafe-inline' http: 'sha256-abc...def',它可能
看起来
script-src 'unsafe-inline' 会是所需策略的一个子集。然而,
hash-source 表达式的存在意味着
'unsafe-inline' 在
所需策略中会被忽略,因此后一策略实际上会允许比前一策略更多的内容,尽管
表面上看并非如此。
这里,我们将把“至少一样严格”这一概念称为“包含”。如果一个 内容安全策略对象(A) 允许 B 允许的所有内容,则称其 包含另一个 (B)。在这种情况下, A 包含 B, 或者 B 被 A包含。
不过,我们并不总是在比较单个策略。当 CSP 列表中存在多个策略时, 它们会产生一种组合效果,该效果在 “多个策略的效果”中描述。这里, 我们将把一个 CSP 列表的组合效果称为它们的交集。详细内容在下文 § 3.1 交集中说明。
在定义了交集之后,我们可以说,A 包含一个 CSP 列表,当且仅当 A 包含该列表的交集。
3.1. 交集
3.1.1. CSP 列表交集
对于一个源 (origin),一个 CSP 列表(list)的 交集是一个单一的内容安全策略对象,表示它们的 净效果,由以下算法生成:
注:并不总是能将多个策略的交集表示为
单个策略。例如,考虑 script-src 'unsafe-inline' 和
script-src 'nonce-abc':
前者只允许内联脚本,后者只允许带有特定令牌的内联或外部化脚本。
其净效果(仅允许带有特定令牌的内联脚本)无法
用单个策略创建。目前,处理这类策略暂且留给
读者练习。
我们不应让 读者做这个练习。我们还应提供关于如何 处理无法表示为单个策略的交集的指导(例如,通过维护一个 策略列表)。
-
对于 list 中的每个 policy:
-
返回 result。
«
"default-src 'self' http://example.com http://example.net; connect-src 'none';",
"connect-src http://example.com/; script-src http://example.com/",
"style-src 'self'; script-src http://example.com/ http://example.net",
»
是通过解析以下序列化 CSP所创建的策略:
"default-src 'self' http://example.com http://example.net; connect-src 'none'; script-src http://example.com/; style-src 'self'"
初始列表中指定的每个策略都包含该 交集。
3.1.2. 策略交集
对于一个源(origin),两个 内容安全策略对象(A 和 B)的 交集是一个单一的 内容安全策略对象,表示它们的 组合效果,由以下 算法生成:
-
断言:A 和 B 都具有 "
enforce" 的处置方式。 -
令 directive names 为空集合。
-
对于 A 中的每个 directive:
-
对于 B 中的每个 directive:
-
对于 directive names 中的每个 directive name:
-
如果 directive name 为 "
report-uri"、"report-to",则继续。 -
令 directive A 为 directive name 和 A 的有效 指令值。
-
令 directive B 为 directive name 和 B 的有效 指令值。
-
断言:directive A 和 directive B 不会同时为
null,并且二者的 值要么都是源 列表,要么二者的值都不是 源列表。 -
如果 directive A 或 directive B 中任一项的值不是 源列表,则继续。
我们 需要扩展此定义,以处理不是源列表的内容。 此外,我们应当更精确一些,也许可以定义一个类似“源列表 指令”的术语,以便针对 directive name 进行检查。
-
如果 directive A 为
null: -
如果 directive B 为
null: -
令 directive value 为 directive A 的 值、directive B 的值、directive name 和 origin 的交集。
-
令 directive 为一个新的指令, 具有以下属性:
-
-
返回 policy。
"default-src 'self' http://example.com http://example.net; connect-src 'none';" and "connect-src http://example.com/; script-src http://example.com/"
是通过解析以下序列化 CSP所获得的策略:
"default-src 'self' http://example.com http://example.net; connect-src 'none'; script-src http://example.com/;"
给定的两个策略都包含该交集。例如,
该
交集的
"script-src http://example.com/" 被第一个策略的
"default-src 'self' http://example.com http://example.net" 和第二个策略的
"script-src http://example.com/"包含。
3.1.3. 源列表交集
对于一个
指令名称(name)和一个源(origin),两个源列表的
交集是一个源列表,
表示它们的净效果。如果不存在这样的源列表
(例如,
https://example.com/ 在 A 中,而 https://not-example.com 在
B 中),则该交集将是
列表 « 'none' »。
-
令 effective A 为 A、 name 和 origin 的有效源列表。
-
令 effective B 为 B、 name 和 origin 的有效源列表。
-
如果 effective A 或 effective B 中任一项为 «
'none'»,则返回 «'none'»。 -
如果 effective A 为空,则返回 effective B。
-
如果 effective B 为空,则返回 effective A。
-
令 schemes 为空集合。
-
令 intersection 为空源列表。
-
对于 effective B 中的每个 expression B:
-
如果 expression B 匹配
scheme-source语法,并且 expression B 包含在 effective A 中,则将 expression B追加到 schemes。注:获取上面的有效源列表意味着 匹配
scheme-source语法的令牌已经 被规范化,使得 "http:"/"ws:" 永远不会在没有 "https:"/"ws:" 同时出现的情况下出现。
-
-
对于 schemes 中的每个 expression:
-
对于 effective A 中的每个 expression A:
-
如果 expression A 匹配
scheme-source语法,并且 schemes 包含 expression A,则继续。 -
对于 effective B 中的每个 expression B:
-
如果 expression A 和 expression B 中至少一个不 匹配
scheme-source或host-source语法:-
如果 expression A 匹配
keyword-source语法,并且是 expression B 的 ASCII 大小写不敏感匹配,则将 expression A追加 到 intersection。 -
如果 expression A 匹配
nonce-source或hash-source语法,并且是 expression B, 则将 expression A追加到 intersection。 -
继续到下一个 expression B。
-
-
如果 expression B 的
scheme-part匹配 schemes 中的某个元素,则继续到下一个 expression B。 -
如果 expression A 和 expression B 的交集 不是
null,则将结果追加到 intersection。
-
-
-
返回 intersection。
intersection 是 A 和 B 的一个交集。
A = wss: http://example.com B = https: wss: 'none' intersection = wss: https://example.com
表达式 "wss:" 出现在两个策略中,因此它出现在它们的交集中。 类似地,"http://example.com" 出现在交集中,因为它是唯一同时被 "http://example.com" 和 "https:" 包含的表达式。请注意,"'none'"" 会被忽略,因为它不是 B 中唯一的令牌。
A = http://sub.a.com http://*.b.com B = https://sub.a.com:* http://*.c.com intersection = https://sub.a.com
只有两个源是相似的:A 中的 "http://sub.a.com" 与 B 中的 "https://sub.a.com:*" 相似, 因此两个源列表的交集是 "https://sub.a.com"。
A = 'unsafe-inline' http://example.com:443/page1/html 'nonce-abc' B = 'unsafe-inline' https://example.com:443/ 'strict-dynamic' 'nonce-abc' intersection = 'nonce-abc'
由于 "strict-dynamic" 只认可 nonce-source 和
hash-source 表达式,B
实际上是 "'strict-dynamic' 'nonce-abc'"。
这就是交集为 "'nonce-abc'" 的原因。
3.1.4. 源表达式交集
如果一个源表达式包含两个匹配 scheme-source 或 host-source 语法的
其他表达式 A 和 B 中更具限制性的scheme-part、
host-part、port-part 和 path-part,则称其为这两个表达式的
交集。
Intersect 是 A 和 B 的一个交集。
A = https: B = http: Intersect = https: //http:允许http:和https:,因此https:是交集。
A = http://*.example.com B = https://sub.example.com:* Intersect = https://sub.example.com:443 // A 未指定端口,因此只允许默认端口。
A = http://example.com:80/page1/html
B = https://example.com:443/
Intersect = null
// A 被显式锁定到端口 80,无法匹配 B 同样显式的端口 443。
A = https:
B = http://example.com
Intersect = https://example.com.
A = https://sub.example.com:*
B = http://*.example.com/page.html
Intersect = https://sub.example.com/page.html
3.1.4.1. scheme-source 和
host-source 交集
scheme-source 或
host-source 语法的源表达式(A 和
B),
如果 A 与 B 源表达式相似,则返回它们的交集。否则,
返回 null。
-
如果 A 与 B 不源表达式相似, 则返回
null。 -
令 source 为空字符串。
-
令 scheme A 为 A 的
scheme-part(如果存在),否则为null。 -
令 scheme B 为 B 的
scheme-part(如果存在),否则为null。 -
如果 scheme A 与 scheme B 不
scheme-part匹配,则令 more secure scheme B 为true;否则为false。 -
如果 scheme A 不是
null且 more secure scheme B 为false,则将 scheme A 和 ":" 追加到 source。否则,如果 scheme B 不是null,则将 scheme B 和 ":" 追加到 source。 -
如果 A 和 B 都匹配
scheme-source语法,则返回 source。 -
如果 source 不为空,则将 "//" 追加到 source。
-
令 host A 为 A 的
host-part(如果存在),否则为null。 -
令 host B 为 B 的
host-part(如果存在),否则为null。 -
如果 host A 不是
null:-
如果 host B 为
null,则将 host A 追加到 source。继续到主 算法中的下一步。 -
如果 A 不匹配
scheme-source语法 且不具有 通配主机,则将 host A 追加到 source。 -
否则,将 host B 追加到 source。
-
-
如果 host A 为
null,则将 host B 追加到 source。 -
令 port A 为 A 的
port-part(如果存在),否则为null。 -
令 port B 为 B 的
port-part(如果存在),否则为null。 -
如果 port A 为
null,则在 port B 不是null时,将 ":" 和 port B 追加到 source。 -
如果 port A 不是
null:-
如果 port B 为
null,则将 ":" 和 port A 追加到 source。继续到主算法中的 下一步。 -
如果 A 不具有通配端口,且 more secure scheme B 为
false,则将 ":" 和 port A 追加到 source。 -
否则,将 ":" 和 port B 追加到 source。
-
-
令 path A 为 A 的
path-part(如果存在),否则为null。 -
令 path B 为 B 的
path-part(如果存在),否则为null。 -
如果 path A 为
null,则在 path B 不是null时,将 path B 追加到 source。 -
如果 path A 不是
null:-
如果 path B 为
null,则返回将 path A 追加到 source 的结果。 -
如果 path A 与 path B
path-part匹配,则将 path A 追加到 source。 -
否则,将 path B 追加到 source。
-
-
返回 source。
3.1.5. 交集辅助项
3.1.5.1. 有效指令值
-
对 name 进行 switch,并执行相关联的步骤:
- "
child-src"- "
connect-src"- "
font-src"- "
img-src"- "
manifest-src"- "
media-src"- "
object-src"- "
script-src"- "
style-src" - "
- "
script-src-elem"- "
script-src-attr" - "
- "
style-src-elem"- "
style-src-attr" - "
- "
frame-src" - "
worker-src" - "
base-uri"- "
block-all-mixed-content"- "
default-src"- "
frame-ancestors"- "
form-action"- "
plugin-types"- "
report-uri"- "
require-sri-for"- "
sandbox"- "
upgrade-insecure-requests" - "
- "
-
返回
null。
3.1.5.2. 有效源列表
'self' 和 * 这样的复杂令牌,并移除无效、已被取代或无效的
令牌(例如,在存在 nonce 的情况下的 'unsafe-inline')。
运行以下步骤的结果通常会比 list 更冗长,但会
显著更易于比较:
-
如果 list 为 空或为 « 'none' »,则返回 « 'none' »。
-
令 result 为空源 列表。
-
对于 list 中的每个 expression:
-
如果 expression 是 "
'self'":-
将给定 origin 执行 § 4.2.1 将 'self' 重写为针对 origin 的 host-source 表达式。 的结果追加到 result。
-
继续。
-
-
如果 expression 匹配
keyword-source语法, 且 name 不是 "script-src" 或 "style-src",则继续。 -
如果以下任一陈述为真,则继续:
-
expression 是 "
'none'" -
expression 是 "
'strict-dynamic'", 且 name 不是 "script-src" -
expression 匹配
nonce-source或hash-source语法,且 name 不是 "script-src" 或 "style-src" -
expression 是 "
'unsafe-inline'", name 是 "script-src,且 list 包含一个或多个匹配以下任一项的令牌:nonce-source语法、hash-source语法,或 "'strict-dynamic'" -
expression 匹配
host-source或scheme-source语法,name 是 "script-src",且 list 包含令牌 "'strict-dynamic'" -
name 是 "
plugin-types",且 expression 不匹配serialized-source-list语法
-
-
如果 expression 是 U+002A ASTERISK 字符(
*): -
如果 expression 匹配 scheme-source 语法:
-
如果 expression 匹配 host-source 语法:
-
如果 expression 的scheme-part 是 "http",则将 "https://"、expression 的host-part、 expression 的 port-part 和 expression 的path-part 的串联结果追加到 result。
-
如果 expression 的scheme-part 是 "ws",则将 "wss://"、expression 的host-part、 expression 的 port-part 和 expression 的path-part 的串联结果追加到 result。
-
-
将 expression追加到 result。
-
-
如果 result 为 空或为 « 'strict-dynamic' »,则返回 « 'none' »。
-
返回 result。
对于源为 https://example.test/ 的任何指令:
https: wss: 'none' 'self'有效源列表是 "http: wss: https://example.test/"。请注意,"'none'" 不是 有效源列表的一部分,因为当它不是唯一的源时没有效果。
对于 "style-src":
http://example.com 'strict-dynamic' 'nonce-abc'有效源列表是 "http://example.com 'nonce-abc'",因为 "'strict-dynamic'" 在非 "
script-src" 指令中会被忽略。
对于 "script-src":
http://example.com 'strict-dynamic' 'nonce-abc'有效源列表是 "'strict-dynamic' 'nonce-abc'",因为在 "
script-src" 情况下,"'strict-dynamic'" 不认可主机和方案源表达式。
3.1.5.3. 源表达式相似性
如果一个源表达式(A)是
B,或者如果它们语法的相关部分
匹配(例如,在 scheme-source 表达式的情况下,
各自的scheme-part 必须在一个方向或另一个方向上
scheme-part 匹配,则称该源表达式
源表达式
相似于另一个
源表达式(B)。
注:此属性是对称的。也就是说,如果 A 与 B源表达式相似,那么 B 也将与 A源表达式相似。
如果一个源表达式的源表达式的host-part的第一个
字符是 U+002A ASTERISK 字符(*),则它具有通配主机。
如果一个源表达式的port-part是 U+002A ASTERISK 字符(*),
则它具有通配端口。
-
如果 A 的语法不匹配 B 的语法,则返回 "
Not Similar"。 -
如果 A 匹配
keyword-source、nonce-source或hash-source语法:-
如果 A 是 B,则返回 "
Similar"。 -
返回 "
Not Similar"。
-
-
令 scheme A 为 A 的
scheme-part(如果存在),否则为null。 -
令 scheme B 为 B 的
scheme-part(如果存在),否则为null。 -
如果 scheme A 不与 scheme Bscheme-part 匹配,且 scheme B 也不与 scheme Ascheme-part 匹配,则返回 "
Not Similar"。 -
如果 A 或 B 匹配
scheme-source语法,则返回 "Similar"。 -
令 host A 为 A 的
host-part(如果存在),否则为null。 -
令 host B 为 B 的
host-part(如果存在),否则为null。 -
令 port A 为 A 的
port-part(如果存在),否则为null。 -
令 port B 为 B 的
port-part(如果存在),否则为null。 -
令 path A 为 A 的
path-part(如果存在),否则为null。 -
令 path B 为 B 的
path-part(如果存在),否则为null。 -
如果以下任一项为真,则返回 "
Not Similar":-
A 和 B 都具有通配主机,但 host A 与 host B 不是 ASCII 大小写不敏感匹配。
-
A 和 B 中至多一个具有通配主机,host A 不与 host B
host-part匹配,且 host B 也不与 host Ahost-part匹配。 -
A 和 B 都不具有通配端口,port A 不与 port B
port-part匹配, 且 port B 也不与 port Aport-part匹配。 -
path A 不与 path B
path-part匹配, 且 path B 也不与 path Apath-part匹配。
-
-
返回 "
Similar"。
A = 'nonce-ch4hvvbHDpv7xCSvXCs3BrNggHdTzxUA' B = 'nonce-ch4hvvbHDpv7xCSvXCs3BrNggHdTzxUA'
由于 A 和 B 都匹配 nonce-source 语法,且 A 是
B,因此 A 与 B 相似。
A = https://inner.example.com/foo/ B = http://*.example.com/foo/bar/
由于 A 具有通配主机,它会匹配任意子域;在此情况下 该子域是 "inner",因此 A 与 B 相似。
A = http://*.example.com B = https://inner.example.com:*
尽管 A 和 B 的端口不同,A 和 B 仍然 相似,因为 "http" 同时匹配 "http" 及其更安全的变体 "https"。
A = http://example.com:80/page1/html B = https://example.com:443/
由于 A 和 B 显式指定了不同端口,A 与 B 不相似。
A = 'sha256-abc123' B = 'sha512-cde456'
尽管 A 和 B 都匹配 hash-source 语法,A 并不
匹配
B,因为哈希值不匹配。
A = http://example.com:80 B = http://example.com:334
在这种情况下,A 和 B 的端口不匹配,因此这两个源 不相似。
A = http://example.com/page.html B = http://example.com/index.html
这两个源不相似,因为它们的路径不匹配。
3.2. 包含
3.2.1. CSP 列表包含
Subsumes",否则返回 "Does Not Subsume"。
-
如果 subsuming policy 为
null,则返回 "Subsumes"。 -
如果 subsuming policy 的处置方式为 "
report",则返回 "Subsumes"。 -
如果 policy list 为 空或为
null,则返回 "Does Not Subsume"。 -
令 effective policy 为给定 policy list 和 origin 执行 § 3.1.1 CSP 列表交集的结果。
-
返回给定 subsuming policy、 subsuming origin、effective policy 和 origin 执行 § 3.2.2 策略包含的结果。
3.2.2. 策略包含
Subsumes",否则返回
"Does Not Subsume"。
-
对于 A 的指令集中的每个 directive A:
-
令 directive name 为 directive A 的名称。
-
如果 directive name 是 "
default-src"、 "report-uri"、"report-to", 则继续。 -
令 effective directive A 为 directive name 和 A 的有效指令值。
-
令 effective directive B 为 directive name 和 B 的有效指令值。
-
如果 effective directive A 为
null,则继续。 -
如果 effective directive B 为
null,则返回 "Does Not Subsume"。 -
如果 directive A 的名称是 "
frame-ancestors":-
如果 effective directive B 是 « "
'none'" »,则继续。 -
如果 effective directive A 是 « "
'none'" »,则返回 "Does Not Subsume"。 -
对于 effective directive B 中的每个 expression B:
-
令 found match 为
false。 -
对于 effective directive A 中的每个 expression A:
-
如果给定 expression A 和 expression B 执行 § 3.2.4 源表达式 包含返回 "
Subsumes",则将 found match 设为true。跳出此内层循环。
-
-
如果 found match 为
false,则返回 "Does Not Subsume"。
-
-
-
如果 directive A 的名称是 "
plugin-types":-
对于 effective directive B 中的每个 type B:
-
如果 effective directive A 不包含 type B, 则返回 "
Does Not Subsume"。
-
-
-
如果 directive A 的名称是 "
sandbox":-
令 flags A 为给定 effective directive A 解析沙盒指令的结果。
-
令 flags B 为给定 effective directive B 解析沙盒指令的结果。
-
对于 flags A 中的每个 flag:
-
如果 flags B 不包含 flag,则返回 "
Does Not Subsume"。
-
注:如果 $B$ 更具限制性,则策略 $A$ 包含 $B$。 对于沙盒标志,这意味着 $A$ 允许的每项权限也必须被 $B$ 允许。如果 $B$ 省略了 $A$ 包含的某个标志,则对于该特定权限而言,$B$ 并不至少与 $A$ 一样严格。
-
-
否则:
-
如果给定 effective directive A、origin A、directive name、effective directive B、 origin B 和 directive name 执行 § 3.2.3 源列表 包含的结果是 "
Does Not Subsume",则返回 "Does Not Subsume"。
-
-
-
返回 "
Subsumes"。
3.2.3. 源列表包含
Subsumes",否则返回
"Does Not Subsume"。
如果一个给定源表达式被某指令的 值包含,则称该 指令包含该源表达式。
-
如果 directive A 与 directive B 不是ASCII 大小写不敏感匹配,则返回 "
Does Not Subsume"。 -
如果 A 为空或 B 为
none,则返回 "Subsumes"。 -
如果 B 为空或 A 为
none,则返回 "Does Not Subsume"。 -
如果 directive B 是 "
script-src",且 B 包含一个keyword-source表达式 "strict-dynamic",但 A 不包含它, 则返回 "Does Not Subsume"。 -
如果 directive B 是 "
script-src" 或 "style-src":-
如果 B 包含一个
keyword-source表达式 "unsafe-eval", 但 A 不包含它,则返回 "Does Not Subsume"。 -
如果 B 包含一个
keyword-source表达式 "unsafe-hashed-attributes",但 A 不包含它,则返回 "Does Not Subsume"。 -
如果 directive B 是 "
script-src",则令 type B 为 "script",否则为 "style"。 类似地,如果 directive A 是 "script-src",则令 type A 为 "script",否则为 "style"。 -
如果给定 B 和 type B,内容安全策略 3 § 6.7.3.2 源列表是否允许 type 的所有内联行为?返回 "
Allows", 但给定 A 和 type A 时返回 "Does Not Allow",则返回 "Does Not Subsume"。
-
-
令 list A 和 list B 为空列表。
-
对于 A 中的每个 expression A:
-
如果 expression A 是 "
self",则将给定 origin A 由 § 4.2.1 将 'self' 重写为针对 origin 的 host-source 表达式。返回的一个host-source追加到 list A。 -
如果 expression A 匹配 U+002A ASTERISK 字符(
*), 则将以下scheme-source表达式追加到 list A: "ftp:"、"http:"、"https:"、 "ws:"、"wss:",以及 origin A 的scheme。-
如果 directive A 是 "
img-src" 或 "media-src",则将一个scheme-source表达式 "data:" 追加到 list A。 -
如果 directive A 是 "
media-src",则将一个scheme-source表达式 "blob:" 追加到 list A。 -
继续到下一个 expression A。
-
-
如果 expression A 不匹配
keyword-source语法, 则将 expression A 追加到 list A。
-
-
对于 B 中的每个 expression B:
-
如果 expression B 是 "
self",则将给定 origin B 由 § 4.2.1 将 'self' 重写为针对 origin 的 host-source 表达式。返回的一个host-source追加到 list B。 -
如果 expression B 匹配 U+002A ASTERISK 字符(
*), 则将以下scheme-source表达式追加到 list B: "ftp:"、"http:"、"https:"、 "ws:"、"wss:",以及 origin B 的scheme。-
如果 directive B 是 "
img-src" 或 "media-src",则将一个scheme-source表达式 "data:" 追加到 list B。 -
如果 directive B 是 "
media-src",则将一个scheme-source表达式 "blob:" 追加到 list B。 -
继续到下一个 expression B。
-
-
如果 expression B 不匹配
keyword-source语法, 则将 expression B 追加到 list B。
-
-
如果 list B 为空,则返回 "
Subsumes"。 -
如果 list A 为空,则返回 "
Does Not Subsume"。 -
对于 list B 中的每个 expression B:
-
如果 expression B 匹配
hash-source语法,或nonce-source语法,除非 directive A 是 "script-src" 或 "style-src",否则继续到下一个表达式。 -
令 found match 为
false。 -
对于 list A 中的每个 expression A:
-
如果给定 expression A 和 expression B 执行 § 3.2.4 源表达式 包含返回 "
Subsumes",则将 found match 设为true。 跳出此内层循环。
-
-
如果 found match 为
false,则返回 "Does Not Subsume"。
-
-
返回 "
Subsumes"。
script-src"。考虑以下
示例:
A = "http://example.com 'sha256-xzi4zkCjuC8'" B = "http://example.com"
由于 B 不允许 hash-source 表达式,但它的值
可在
A 中找到,因此 A 包含 B。不过,B
包含 A
并不成立。
A = "https://example.com 'sha256-xzi4zkCjuC8'" B = "http://example.com"
在这种情况下,A 不包含 B,因为 "https://example.com" 不包含 "http://example.com"。
A = "http://example.com 'sha256-xzi4zkCjuC8'" B = "http://example.com 'unsafe-inline'"
由于 B 允许所有内联行为,但 A 不允许,因此 A 不包含 B。
A = "http://example.com 'sha256-xzi4zkCjuC8' 'strict-dynamic'" B = "http://example.com 'unsafe-inline' 'strict-dynamic'"
3.2.4. 源表达式包含
Subsumes",否则返回
"Does Not Subsume"。
-
断言:A 和 B 均不匹配
keyword-source语法。 -
如果 A 和 B 都匹配
host-source或scheme-source语法:-
如果给定 A 的
scheme-part(如果 A 不包含scheme-part则为null)和 B 的scheme-part(如果 B 不包含scheme-part则为null),内容安全策略 3 § 6.7.2.9 scheme-part 匹配返回 "Does Not Match",则返回 "Does Not Subsume"。 -
如果 A 或 B 匹配
scheme-source语法:-
如果 A 匹配
scheme-source语法,则返回 "Subsumes"。 否则,返回 "Does Not Subsume"。
-
-
如果 B 具有
wildcard host:-
如果 A 不具有
wildcard host,则返回 "Does not Subsume"。 -
令 remaining host B 为从 B 的
host-part中移除前导 ("*.") 的结果。 -
如果给定 A 的
host-part和 remaining host B,内容安全 策略 3 § 6.7.2.10 host-part 匹配返回 "Does Not Match",则返回 "Does Not Subsume"。
-
-
如果 B 不具有
wildcard host,且 给定 A 的host-part和 B 的host-part,内容安全策略 3 § 6.7.2.10 host-part 匹配返回 "Does Not Match",则返回 "Does Not Subsume"。 -
如果 B 具有
wildcard port,但 A 不具有wildcard port,则返回 "Does Not Subsume"。 -
如果 B 不具有
wildcard port,且 给定 A 的port-part(如果 A 不包含port-part则为null)和 B 的port-part(如果 B 不包含port-part则为null),内容安全策略 3 § 6.7.2.11 port-part 匹配返回 "Does Not Match",则返回 "Does Not Subsume"。 -
如果给定 A 的
path-part(如果 A 不包含path-part则为null)和 B 的path-part(如果 B 不包含path-part则为null),内容安全策略 3 § 6.7.2.12 path-part 匹配返回 "Does Not Match",则返回 "Does Not Subsume"。 -
返回 "
Subsumes"。
-
-
如果 A 和 B 都匹配
hash-source语法:-
如果 A 是 B,则返回 "
Subsumes"。否则,返回 "Does Not Subsume"。
-
-
如果 A 和 B 都匹配
nonce-source语法:-
返回 "
Subsumes"。
注:Nonce 源匹配与值无关,以防止恶意嵌入方通过 § 6.1 策略泄漏中描述的攻击暴力破解 nonce 值。
-
-
返回 "
Does Not Subsume"。
应更新针对 nonce 和哈希的包含关系,以考虑更宽松的源 (例如与 'unsafe-inline' 组合的 'self' 或 '*')在逻辑上包含它们的情况。
4. 算法
4.1. response 对 request 是否被 requiredCSP 阻止?
给定一个响应(response)、一个请求
(request)和一个序列化 CSP-或-null
(requiredCSP),此算法视情况返回 "Allowed" 或 "Blocked":
-
如果 requiredCSP 为
null,则返回 "Allowed"。 -
令 required policy 为将 requiredCSP 作为 "
enforce" 解析的结果。 -
如果在 response 和 request 上执行 § 4.2 response 是否允许来自 request 的策略进行全面强制执行? 算法返回 "
Allowed",则返回 "Allowed"。 -
如果在 required policy、response 的url 的源、解析 response 的内容安全 策略的结果,以及 response 的url 的源上执行 § 3.2.1 CSP 列表包含算法时返回 "
Subsumes",则返回 "Allowed"。 -
返回 "
Blocked"。
4.2. response 是否允许来自 request 的策略进行全面强制执行?
给定一个响应(response)和一个请求
(request),如果前者允许后者强制执行任意策略,则此算法返回
"Allowed",
否则返回
"Not Allowed":
-
如果 response 的url 的scheme是本地 scheme,则返回 "
Allowed"。注:本地 scheme 响应已经从嵌入方继承其策略,因此我们 允许嵌入方通过此嵌入机制收紧该策略。
-
如果 response 的标头列表具有一个名为 `
Allow-CSP-From` 的标头(header): -
返回 "
Not Allowed"。
4.2.1.
将 'self' 重写为针对 origin 的 host-source 表达式。
给定一个源(origin),此算法返回一个host-source 表达式,其
效果与该源的 'self' 相同:
5. 安全考量
5.1. 威胁模型
本提案旨在让文档能够控制其嵌入的子框架中所使用的内容。由于框架通常 本身就被视为安全边界,并有意限制跨源访问,因此我们应确保避免削弱该边界。
因此,我们的目标是:
-
允许嵌入方对其嵌入的任意文档施加内容安全策略要求。
-
确保使用给定内容安全策略要求加载的文档不能轻易 逃避它(例如,通过其控制下的本地 scheme文档)。将 要求放入每个上下文的策略容器中,使我们能够依赖与 CSP 本身相同的继承 结构,并且还提供了一个清晰的点,可从该点继续把这些 要求施加到被嵌入方自身可能选择嵌入的文档上。
-
确保使用给定内容安全策略要求加载的文档能够看到 被施加的要求,并显式选择加入(通过 `
Allow-CSP-From` 标头)或隐式选择加入(通过 § 3 隐式策略接受中概述的机制)。这可以保护被嵌入方免受 可能禁用关键安全基础设施的恶意策略要求影响。 -
当然,还要在不引入新漏洞或风险的情况下完成上述目标。
本提案无意取代现有隔离机制,例如 CSP 的
frame-ancestors 或 X-Frame-Options,也不会为被嵌入方提供
超出
iframe
元素已有含义之外的任何保护。
5.2. 策略强制执行
被嵌入文档应谨慎评估所提出的内容安全策略,而不是 简单反射嵌入方建议的任何策略。这样做可能使聪明的攻击者能够 选择性禁用网站代码中对其自身保护至关重要的部分。
特别是,不期望被嵌入的文档应继续对任何此类
请求作出响应,返回包含适当 frame-ancestors
指令的内容安全策略。
5.3. 标头反射
服务器应谨慎对待盲目地将
`Sec-Required-CSP`
标头反射到
`Content-Security-Policy`
响应标头中,因为所施加的策略可能以意想不到的方式限制最终
上下文,或者比服务器原本会应用的策略更弱。
相反,鼓励开发者在接受传入策略之前先评估它们,方法是为受信任源
交付
`Allow-CSP-From`
标头。这确保了页面
希望应用的策略会被应用,同时在其上叠加所需策略。由于多个策略
只会收紧限制,这将确保服务器的策略仍然是可靠的
基线保护。
5.4. 标头长度
此特性允许嵌入方控制
跨源嵌入文档请求的
`Sec-Required-CSP`
HTTP 标头值。如果服务器对 HTTP
标头长度强制执行限制,这可能导致网络错误。攻击者可以滥用这一点来强制缓存
失效,参见
https://xsleaks.dev/docs/attacks/cache-probing/#cache-probing-with-error-events。
因此,我们将有效 csp 属性的最大长度限制为 4 KB。
6. 隐私考量
6.1. 策略泄漏
该强制执行机制允许恶意嵌入方通过暴力尝试页面策略的约束来跨源读取 该页面的策略。如果策略包含秘密令牌或用户名,这可能泄漏关于页面或加载 该页面的用户的有趣数据。
同样,这里的最佳防御是通过适当的 frame-ancestors 指令控制允许嵌入给定资源的上下文。
6.2. 数据外泄
此特性允许嵌入方通过
`Sec-Required-CSP`
HTTP 标头向第三方端点发送信息。这似乎不会暴露任何不能
通过 HTTP 请求本身(通过 GET 参数等)隧道传输的信息,并且嵌入方仍可通过使用
适当 child-src 指令强制执行内容安全策略,来控制
此类请求可以发送到哪些端点。
7. 编写考量
7.1.
要求 'self'
处理required CSP 时,关键字 'self'
指的是正被加载到子可导航对象中的 URL 的源,而不是
要求该策略的文档的源。
https://example.com/page.html 的页面上要求一个包含 'self' 的策略:
<iframe src="https://advertisements-r-us.example.com/ad1.cfm"
csp="script-src 'self'">
</iframe>
如果返回的 CSP 是:
Content-Security-Policy: script-src 'self'
那么这个
iframe
元素将会被加载。
然而,如果返回的 CSP 是:
Content-Security-Policy: script-src "https://example.com/"
那么这个
iframe
元素将不会被加载。
8. IANA 考量
永久消息标头字段注册表应使用以下注册信息更新
`Sec-Required-CSP`
标头:[RFC3864]
- 标头字段名称
-
Sec-Required-CSP
- 适用协议
-
http
- 状态
-
standard
- 作者/变更控制者
-
W3C
- 规范文档
同样,注册表也应使用以下注册信息更新
`Allow-CSP-From`
标头:[RFC3864]
- 标头字段名称
-
Allow-CSP-From
- 适用协议
-
http
- 状态
-
standard
- 作者/变更控制者
-
W3C
- 规范文档
-
本规范(参见 § 2.3 Allow-CSP-From HTTP 响应 标头)