1. 简介
有趣的网页应用通常会有大量对外暴露的端点,这些端点可能暴露用户的敏感数据,或以用户身份执行某些操作。由于用户的浏览器很容易被诱导去访问这些端点,并在请求中带上用户现有的凭据(如 cookies、内网特权身份等),因此应用程序在设计这些端点时必须非常谨慎,以防止滥用。
在某些情况下做到谨慎非常困难(如“简单”的CSRF),而在另一些情况(如跨站搜索、计时攻击等)几乎不可能。后一类问题包括基于服务器生成特定响应时的处理时长的计时攻击,以及基于响应长度的各种攻击(包括面向Web的计时攻击和被动网络攻击者)。
如果服务器能够根据请求的方式更智能地决定是否响应,将有助于缓解后一类问题。例如,一个银行服务器上的“转出我所有资金”的端点几乎不可能被放在
img
标签中引用,同样,evil.com 发起任何合法请求的概率也非常低。理想情况下,服务器可以事先拒绝这些请求,而不是将其送到应用后端处理。
在这里,我们介绍了一种机制,用户代理可通过为外发请求添加更多上下文,实现上述决策。通过在一组Fetch 元数据请求头中向服务器传递元数据,应用可以基于若干前置条件快速拒绝请求。这项工作甚至可以提升到应用层之上(如反向代理、CDN等)来完成。
1.1. 示例
一个由
picture
元素生成的请求,会包含如下HTTP请求头:
Sec-Fetch-Dest: image Sec-Fetch-Mode: no-cors Sec-Fetch-Site: cross-site
如果用户点击页面内的链接,从 https://example.com 跳转到 https://example.com/,则会生成包含如下HTTP请求头的请求:
Sec-Fetch-Dest: document Sec-Fetch-Mode: navigate Sec-Fetch-Site: same-origin Sec-Fetch-User: ?1
2. Fetch 元数据请求头
下文定义了多个Fetch 元数据请求头,每个都将某个有意义的请求属性暴露给服务器。
2.1. Sec-Fetch-Dest HTTP请求头
Sec-Fetch-Dest HTTP 请求头会将请求的目标类型暴露给服务器。它是一个结构化字段,其值必须为标记(token)。[RFC9651]
其ABNF如下:
Sec-Fetch-Dest = sf-token
Sec-Fetch-Dest 的有效值包括 请求在 Fetch标准中定义的所有目标类型。
为了支持尚未定义的未来请求类型,若遇到无效值,服务器应忽略此头。
//fetch()的 destination 为空字符串: Sec-Fetch-Dest: empty //<img>的 destination 为 "image" Sec-Fetch-Dest: image //new Worker()的 destination 为 "worker" Sec-Fetch-Dest: worker // 顶层导航的 destination 为 "document" Sec-Fetch-Dest: document //<iframe>导航的 destination 为 "iframe" Sec-Fetch-Dest: iframe
Sec-Fetch-Dest 标头:
2.2. Sec-Fetch-Mode HTTP请求头
Sec-Fetch-Mode HTTP 请求标头向服务器公开请求的
模式。它是一个结构化字段,其值必须为一个
令牌。[RFC9651] 其 ABNF 为:
Sec-Fetch-Mode = sf-token
有效的 Sec-Fetch-Mode 值包括 "cors"、"navigate"、
"no-cors"、"same-origin",
以及 "websocket"。为了支持与尚未知晓的请求类型向前兼容,
如果此标头包含无效值,服务器应忽略此标头。
Sec-Fetch-Mode 标头,对于一个请求 r:
2.3. Sec-Fetch-Site HTTP请求头
Sec-Fetch-Site HTTP 请求标头公开了
请求发起者的源与其目标的源之间的关系。它是一个结构化字段,
其值为一个令牌。[RFC9651] 其 ABNF 为:
Sec-Fetch-Site = sf-token
有效的 Sec-Fetch-Site 值包括 "cross-site"、"same-origin"、
"same-site" 和 "none"。
为了支持与尚未知晓的请求类型向前兼容,如果此标头包含无效值,服务器应忽略
此标头。
Sec-Fetch-Site 标头:
-
将 header 的值设置为
same-origin。 -
如果 r 是一个由用户与 用户代理的交互显式引起的导航请求(例如,直接在用户代理中输入地址,或 点击书签等),则将 header 的值设置为
none。注: 有关这个定义得不太明确的步骤的更多细节, 请参阅§ 4.3 直接由用户发起的请求。
-
如果 header 的值不是
none,则对于 r 的URL 列表中的每个 url: -
在 r 的标头列表中设置结构化字段值 `
Sec-Fetch-Site`/header。
2.4. Sec-Fetch-User HTTP请求头
Sec-Fetch-User HTTP 请求标头公开了
导航请求是否由用户激活触发。它是一个结构化字段,其
值为一个布尔值。[RFC9651] 其 ABNF 为:
Sec-Fetch-User = sf-boolean
注: 此标头仅随导航请求传递,并且仅当其值为 true 时传递。
如果我们能够明确说明一些通过公开该信息而得到改善的用例
(并且如果我们能够就如何为所有我们感兴趣的子资源请求类型定义该状态达成一致),
那么将来将这些标头的适用范围扩展为普遍包含子资源请求或许是合理的,
但就目前而言,导航请求有明确的用例,并且似乎
可以直接以可互操作的方式定义。
Sec-Fetch-User 标头:
3. 与 Fetch 和 HTML 的集成
为了支持 Sec-Fetch-User,请求具有一个用户激活,其值为 false,
除非由 HTML 的通过 fetch 创建导航参数算法另行填充。
Fetch 元数据标头会在 Fetch 的“HTTP-network-or-cache” 算法中附加到传出的请求,使用以下步骤。有关集成细节,请参阅该规范[FETCH]。
4. 安全与隐私注意事项
4.1. 重定向
用户代理将随重定向链中的每个
请求一起发送 Sec-Fetch-Site 标头。
在存在跨源或跨站点
重定向时,该标头的值将发生变化,以减少混淆。
设置 Sec-Fetch-Site 标头的算法会遍历请求的整个
URL 列表,如果列表中的任何 URL 与
请求的当前 URL跨站点,则发送 cross-site;仅当列表中的所有 URL
与请求的当前 URL同站点时,才发送 same-site;仅当列表中的所有 URL
与请求的当前 URL同源时,才发送 same-origin。
例如,如果 https://example.com/ 请求 https://example.com/redirect,则
初始
请求的 Sec-Fetch-Site 值将为 same-origin。如果该响应重定向
到
https://subdomain.example.com/redirect,则该请求的 Sec-Fetch-Site 值将
为
same-site(因为 https://subdomain.example.com/ 和 https://example.com/
具有相同的
可注册域)。如果该响应重定向到 https://example.net/redirect,则该
请求的 Sec-Fetch-Site 值将为 cross-site(因为
https://example.net/ 与
https://example.com/ 和 https://subdomain.example.com/ 不同站点)。如果该
响应
一路重定向回 https://example.com/,则最终请求的
Sec-Fetch-Site
值仍将为 cross-site(因为重定向链中包含
https://example.net/,它
仍然与其他 URL 不同站点。
注: 对于
Sec-Fetch-Site: None 这一特殊情况,为了支持将短链接复制/粘贴到地址
栏这一常见情形,在重定向过程中保持该值似乎是合理的。也就是说,如果用户代理选择将地址栏导航到 https://sho.rt/link
视为 Sec-Fetch-Site: none,那么重定向后的导航到
https://target.com/long/path/goes/here
同样应断言 Sec-Fetch-Site: none。
4.2. Sec- 前缀
本文档中定义的每个标头都以 Sec- 为前缀,这使它们全部成为
禁止的响应标头名称,因此
无法通过 JavaScript 修改。这将防止
恶意网站诱使用户代理随请求发送伪造的元数据,
从而应能让站点更有信心根据
所声明的信息作出合理响应。
4.3. 用户直接发起的请求
在设置 Sec-Fetch-Site 标头时,要求用户代理
区分“由用户交互显式引起”的导航请求。这个
定义得不太明确的短语取自 HTML,其中
提出
“除了本规范中定义的方式之外,用户代理还可以提供各种方式,让用户显式地使浏览上下文
导航。”
其目标是区分由给定的(可能具有
恶意意图的!)网站控制的“Web 式”导航(例如链接、window.location setter、表单提交等),以及那些
不受网站控制的导航(例如用户与用户代理的地址栏、书签等进行的交互)。前者
将随一个 Sec-Fetch-Site 标头一起传递,其值根据情况为 same-origin、
same-site 或
cross-site。后者将通过值 none 加以区分,
因为实际上并没有
特定站点对该请求负责,并且允许服务器将
这些请求视为可信请求是合理的,因为它们以某种方式代表了用户的直接意图。
每个用户代理可能都有一组不同的交互方式,这些交互可能属于其中一个或另一个 类别,并且很难为这些情况共享自动化测试套件。不过, 最好还是能在那些可能比较常见的行为上保持一致。下面是一些示例:
-
从地址栏导航:一般情况下,这种导航应 被视为直接由用户发起,并包含
Sec-Fetch-Site: none。用户代理 围绕向地址栏粘贴值采用启发式规则可能是合理的(特别是在 “复制”操作能够追溯到特定源的情况下),并将这种导航与 用户自行键入内容产生的导航区别对待。 -
从用户代理 UI 导航(书签、新标签页等):用户与 用户代理 UI 中链接的交互应采用与其在用户代理地址栏中的输入类似的方式处理, 包含
Sec-Fetch-Site: none,以标明该导航由用户发起。 -
从链接的上下文菜单导航(例如“在新窗口中打开”):由于链接的目标 由该链接所在的页面控制,因此用户代理应将该导航视为 由站点控制,并根据 控制该链接的站点与将要打开的站点之间的关系适当地设置
Sec-Fetch-Site标头。 -
按住 Ctrl 点击链接:这里适用的论据和结论与上面直接讨论的链接上下文 菜单相同。
-
通过历史记录导航(例如用户代理的“后退”按钮):
-
拖放:这里根据 被拖动内容的来源区分行为似乎是合理的。如果内容从某个标签页拖出,用户代理应能够确定 其源,并相应地设置
Sec-Fetch-Site。如果内容从其他地方拖出 (用户代理的书签栏、完全不同的另一个应用等),那么Sec-Fetch-Site: none可能是 合适的。
4.4. 扩展发起的请求
一些用户代理支持能够发出请求的扩展,并且这些扩展可以被赋予 超出普通 Web 内容的能力,以便让用户更好地控制其 Web 使用体验。虽然这些扩展 在某种程度上超出了 Web 平台的范围,但仍鼓励用户代理仔细考虑 应如何向服务器表示这些请求。一般而言,用户代理最好能够 满足以下两个目标:
-
对于给定站点不具有特殊权限的扩展,不能发出会绕过 该站点服务器端 Fetch 元数据逻辑的请求。
-
开发者能够识别扩展发出的请求,从而在服务器选择这样做时,可以将这些请求排除在其 Fetch 元数据逻辑之外。这增强了开发者有信心部署 Fetch 元数据保护措施的能力,同时不会损害用户的合法利益。
基于这些目标,鼓励用户代理实现以下行为:
-
如果扩展无权访问给定 URL,则它向该 URL 发出的请求可以 包含一个值为
cross-site的Sec-Fetch-Site标头,就像 普通 Web 请求一样。 如果扩展确实有权访问给定 URL,则Sec-Fetch-Site的值可以为same-origin。 -
从扩展上下文发出的传出请求中可以包含
Origin标头,其 由实现定义的值应允许服务器 区分由扩展发起的请求 与由 Web 发起的请求。
5. 部署注意事项
5.1. Vary
如果给定端点的响应取决于客户端在
Fetch 元数据
标头中提供的值,则开发者应谨慎地包含适当的 Vary 标头
[RFC9110],以确保
缓存能够正确处理该响应。例如,
Vary: Accept-Encoding, Sec-Fetch-Site。
5.2. 头部冗余
本文档的早期版本定义了单个 Sec-Metadata 标头,其内容为
一个字典。后续讨论(以及 Mark Nottingham 出色的
[mnot-designing-headers])将设计从
单个字典转变为一系列简单的
标头,每个标头仅包含单个令牌。采用这种设计后,在
HTTP 当前的 HPACK 压缩算法下,其性能应显著更好。
有关该主题的进一步讨论可参见以下评审线程: w3ctag/design-reviews#280。
6. IANA 注意事项
永久消息标头字段注册表应更新,以加入以下 Fetch 元数据 标头注册项:[RFC3864]
6.1. Sec-Fetch-Dest 登记
- 头字段名称
-
Sec-Fetch-Dest
- 适用协议
-
http
- 状态
-
standard
- 作者/变更控制者
-
Me
- 规范文档
-
本规范(参见 § 2.1 Sec-Fetch-Dest HTTP 请求头)
6.2. Sec-Fetch-Mode 登记
- 头字段名称
-
Sec-Fetch-Mode
- 适用协议
-
http
- 状态
-
standard
- 作者/变更控制者
-
Me
- 规范文档
-
本规范(参见 § 2.2 Sec-Fetch-Mode HTTP 请求头)
6.3. Sec-Fetch-Site 登记
- 头字段名称
-
Sec-Fetch-Site
- 适用协议
-
http
- 状态
-
standard
- 作者/变更控制者
-
Me
- 规范文档
-
本规范(参见 § 2.3 Sec-Fetch-Site HTTP 请求头)
6.4. Sec-Fetch-User 登记
- 头字段名称
-
Sec-Fetch-User
- 适用协议
-
http
- 状态
-
standard
- 作者/变更控制者
-
Me
- 规范文档
-
本规范(参见 § 2.4 Sec-Fetch-User HTTP 请求头)
7. 致谢
感谢 Anne van Kesteren、Artur Janc、Dan Veditz、Łukasz Anforowicz、Mark Nottingham 和 Roberto Clapis,他们为该机制设计提供了大量支持。