防止 CSRF、XSSI 和跨域信息泄露。
为什么您应该关注隔离您的 Web 资源?
许多 Web 应用程序都容易受到跨域攻击的影响,例如跨站请求伪造 (CSRF)、跨站脚本包含 (XSSI)、计时攻击、跨域信息泄露或推测执行侧信道 (Spectre) 攻击。
Fetch Metadata 请求标头允许您部署强大的纵深防御机制(资源隔离策略),以保护您的应用程序免受这些常见的跨域攻击。
通常,给定 Web 应用程序公开的资源仅由应用程序本身加载,而不由其他网站加载。在这种情况下,部署基于 Fetch Metadata 请求标头的资源隔离策略几乎不需要任何工作,同时还可以保护应用程序免受跨站攻击。
浏览器兼容性
所有现代浏览器引擎都支持 Fetch Metadata 请求标头。
背景
许多跨站攻击之所以成为可能,是因为 Web 默认是开放的,并且您的应用程序服务器无法轻易保护自己免受来自外部应用程序的通信。典型的跨域攻击是跨站请求伪造 (CSRF),攻击者将用户诱骗到他们控制的站点,然后向用户登录的服务器提交表单。由于服务器无法判断请求是否来自另一个域(跨站),并且浏览器会自动将 Cookie 附加到跨站请求,因此服务器将代表用户执行攻击者请求的操作。
其他跨站攻击(如跨站脚本包含 (XSSI) 或跨域信息泄露)在本质上与 CSRF 类似,并且依赖于在攻击者控制的文档中加载来自受害者应用程序的资源,并泄露有关受害者应用程序的信息。由于应用程序无法轻易区分受信任的请求和不受信任的请求,因此它们无法丢弃恶意的跨站流量。
Fetch Metadata 简介
Fetch Metadata 请求标头是一项新的 Web 平台安全功能,旨在帮助服务器防御跨域攻击。通过在一组 Sec-Fetch-*
标头中提供有关 HTTP 请求上下文的信息,它们允许响应服务器在处理请求之前应用安全策略。这使开发人员可以根据请求的发出方式及其将要使用的上下文来决定是接受还是拒绝请求,从而可以仅响应其自身应用程序发出的合法请求。

Sec-Fetch-*
标头在 HTTP 请求中提供了额外的上下文,因此服务器可以拒绝恶意的跨站请求。

Sec-Fetch-Site
Sec-Fetch-Site
告诉服务器哪个 站点 发送了请求。浏览器将此值设置为以下值之一
same-origin
,如果请求是由您自己的应用程序发出的(例如,site.example
)same-site
,如果请求是由您站点的子域发出的(例如,bar.site.example
)none
,如果请求是用户与用户代理交互的显式结果(例如,单击书签)cross-site
,如果请求是由另一个网站发送的(例如,evil.example
)
Sec-Fetch-Mode
Sec-Fetch-Mode
指示请求的 模式。这大致对应于请求的类型,并允许您区分资源加载和导航请求。例如,目标为 navigate
表示顶级导航请求,而 no-cors
表示资源请求,例如加载图像。
Sec-Fetch-Dest
Sec-Fetch-Dest
公开请求的 目标(例如,如果 script
或 img
标记导致浏览器请求资源)。
如何使用 Fetch Metadata 防御跨域攻击
这些请求标头提供的额外信息非常简单,但额外的上下文使您可以在服务器端构建强大的安全逻辑(也称为资源隔离策略),只需几行代码即可完成。
实施资源隔离策略
资源隔离策略可防止您的资源被外部网站请求。阻止此类流量可以缓解常见的跨站 Web 漏洞,例如 CSRF、XSSI、计时攻击和跨域信息泄露。此策略可以为应用程序的所有端点启用,并将允许来自您自己的应用程序以及直接导航(通过 HTTP GET
请求)的所有资源请求。旨在在跨站上下文中加载的端点(例如,使用 CORS 加载的端点)可以从此逻辑中选择退出。
步骤 1:允许来自不发送 Fetch Metadata 的浏览器的请求
由于并非所有浏览器都支持 Fetch Metadata,因此您需要通过检查 sec-fetch-site
的存在来允许未设置 Sec-Fetch-*
标头的请求。
if not req['sec-fetch-site']:
return True # Allow this request
步骤 2:允许同站点和浏览器发起的请求
任何并非源自跨域上下文(如 evil.example
)的请求都将被允许。特别是,这些请求是
- 源自您自己的应用程序(例如,同源请求,其中
site.example
请求site.example/foo.json
将始终被允许)。 - 源自您的子域。
- 由用户与用户代理的交互显式引起(例如,直接导航或单击书签等)。
if req['sec-fetch-site'] in ('same-origin', 'same-site', 'none'):
return True # Allow this request
步骤 3:允许简单的顶级导航和 iframe
为确保您的站点仍然可以从其他站点链接,您必须允许简单的 (HTTP GET
) 顶级导航。
if req['sec-fetch-mode'] == 'navigate' and req.method == 'GET'
# <object> and <embed> send navigation requests, which we disallow.
and req['sec-fetch-dest'] not in ('object', 'embed'):
return True # Allow this request
步骤 4:选择退出旨在服务跨站流量的端点(可选)
在某些情况下,您的应用程序可能提供旨在跨站加载的资源。这些资源需要按路径或按端点豁免。此类端点的示例包括
- 旨在跨域访问的端点:如果您的应用程序正在服务
CORS
已启用的端点,则您需要显式地从资源隔离中选择退出这些端点,以确保仍然可以对这些端点进行跨站请求。 - 公共资源(例如,图像、样式等):任何应该可以从其他站点跨域加载的公共和未经身份验证的资源也可以被豁免。
if req.path in ('/my_CORS_endpoint', '/favicon.png'):
return True
步骤 5:拒绝所有其他跨站且非导航请求
任何其他跨站请求都将被此资源隔离策略拒绝,从而保护您的应用程序免受常见的跨站攻击。
示例: 以下代码演示了在服务器上或作为中间件实施强大的资源隔离策略的完整实现,以拒绝潜在的恶意跨站资源请求,同时允许简单的导航请求
# Reject cross-origin requests to protect from CSRF, XSSI, and other bugs
def allow_request(req):
# Allow requests from browsers which don't send Fetch Metadata
if not req['sec-fetch-site']:
return True
# Allow same-site and browser-initiated requests
if req['sec-fetch-site'] in ('same-origin', 'same-site', 'none'):
return True
# Allow simple top-level navigations except <object> and <embed>
if req['sec-fetch-mode'] == 'navigate' and req.method == 'GET'
and req['sec-fetch-dest'] not in ('object', 'embed'):
return True
# [OPTIONAL] Exempt paths/endpoints meant to be served cross-origin.
if req.path in ('/my_CORS_endpoint', '/favicon.png'):
return True
# Reject all other requests that are cross-site and not navigational
return False
部署资源隔离策略
- 安装一个模块(如上面的代码片段),以记录和监控您的站点行为,并确保限制不会影响任何合法流量。
- 通过豁免合法的跨域端点来修复潜在的违规行为。
- 通过删除不合规的请求来强制执行策略。
识别和修复策略违规行为
建议您通过首先在服务器端代码中以报告模式启用策略来以无副作用的方式测试您的策略。或者,您可以在中间件或反向代理中实现此逻辑,反向代理会记录应用于生产流量时您的策略可能产生的任何违规行为。
根据我们在 Google 推出 Fetch Metadata 资源隔离策略的经验,大多数应用程序默认情况下与此类策略兼容,并且很少需要豁免端点以允许跨站流量。
强制执行资源隔离策略
在您检查过您的策略不会影响合法的生产流量之后,您就可以强制执行限制,保证其他站点将无法请求您的资源并保护您的用户免受跨站攻击。
延伸阅读
- W3C Fetch Metadata Request Headers 规范
- Fetch Metadata Playground
- Google I/O 演讲:使用现代平台功能保护 Web 应用(幻灯片)