使用 Fetch Metadata 保护您的资源免受网络攻击

防止 CSRF、XSSI 和跨源信息泄露。

Lukas Weichselbaum
Lukas Weichselbaum

为什么应该关注隔离 Web 资源?

许多 Web 应用都容易受到跨源攻击,例如跨站请求伪造 (CSRF)、跨站脚本包含 (XSSI)、计时攻击、跨源信息泄露或推测执行边信道 (Spectre) 攻击。

借助提取元数据请求标头,您可以部署强大的深度防御机制(资源隔离政策),保护您的应用免受这些常见的跨源攻击。

给定 Web 应用所提供的资源只能由该应用本身加载,而不能由其他网站加载,这种情况很常见。在这种情况下,部署基于 Fetch Metadata 请求标头的资源隔离政策非常简单,同时还可以保护应用程序免受跨站攻击。

浏览器兼容性

所有新型浏览器引擎都支持“提取元数据”请求标头。

浏览器支持

  • Chrome:76。 <ph type="x-smartling-placeholder">
  • Edge:79。 <ph type="x-smartling-placeholder">
  • Firefox:90。 <ph type="x-smartling-placeholder">
  • Safari:16.4. <ph type="x-smartling-placeholder">

来源

背景

由于网络默认是开放的,您的应用服务器无法轻松地保护自己免受源自外部应用的通信,因此可能会发生许多跨网站攻击。 典型的跨源攻击是跨站请求伪造 (CSRF),即攻击者诱使用户访问他们控制的网站,然后将表单提交到用户登录的服务器。由于服务器无法分辨请求是否来自另一个网域(跨网站),并且浏览器会自动将 Cookie 附加到跨网站请求,因此服务器将代表用户执行攻击者请求的操作。

其他跨站点攻击(如跨站脚本收录 (XSSI) 或跨源信息泄露)在本质上与 CSRF 类似,并且依赖于在攻击者控制的文档内加载受害应用的资源,并泄露受害应用的相关信息。由于应用无法轻松区分受信任的请求与不受信任的请求,因此无法舍弃恶意跨站点流量。

“提取元数据”简介

“提取元数据”请求标头是一项新的网络平台安全功能,旨在帮助服务器抵御跨源攻击。通过在一组 Sec-Fetch-* 标头中提供 HTTP 请求的上下文信息,它们可让响应服务器在处理请求之前应用安全政策。这使得开发者能够根据请求提出方式以及请求使用情境来决定是接受还是拒绝请求,从而可以仅响应自己的应用发出的合法请求。

同源
<ph type="x-smartling-placeholder"></ph> 来自您自己的服务器(同源)所提供服务的网站的请求将继续有效。 使用 JavaScript 从 https://site.example 提取资源 https://site.example/foo.json 的请求会导致浏览器发送 HTTP 请求标头“Sec Fetch-Site: same-origin”。
跨网站
<ph type="x-smartling-placeholder"></ph> 服务器可能会因为 Sec-Fetch-* 标头提供的 HTTP 请求中的其他上下文而拒绝恶意跨网站请求。 https://evil.example 上的图片,将 img 元素的 src 属性设置为“https://site.example/foo.json”会使浏览器发送 HTTP 请求标头“Sec-Fetch-Site: cross-site”。

Sec-Fetch-Site

浏览器支持

  • Chrome:76。 <ph type="x-smartling-placeholder">
  • Edge:79。 <ph type="x-smartling-placeholder">
  • Firefox:90。 <ph type="x-smartling-placeholder">
  • Safari:16.4. <ph type="x-smartling-placeholder">

来源

Sec-Fetch-Site 会告知服务器哪个网站发送了请求。浏览器将此值设置为以下某个值:

  • same-origin(如果请求是由您自己的应用发出的,例如 site.example
  • same-site(如果请求是由您网站的子网域(例如 bar.site.example)发出的)
  • 如果请求是由用户与用户代理的互动(例如,点击书签)明确引发的,则为 none
  • cross-site(如果请求是由其他网站(例如 evil.example)发送的)

Sec-Fetch-Mode

浏览器支持

  • Chrome:76。 <ph type="x-smartling-placeholder">
  • Edge:79。 <ph type="x-smartling-placeholder">
  • Firefox:90。 <ph type="x-smartling-placeholder">
  • Safari:16.4. <ph type="x-smartling-placeholder">

来源

Sec-Fetch-Mode 表示请求的模式。这大致对应于请求的类型,使您能够区分资源加载和导航请求。例如,目的地 navigate 表示顶级导航请求,而 no-cors 表示加载图片等资源请求。

Sec-Fetch-Dest

浏览器支持

  • Chrome:80。 <ph type="x-smartling-placeholder">
  • 边缘:80。 <ph type="x-smartling-placeholder">
  • Firefox:90。 <ph type="x-smartling-placeholder">
  • Safari:16.4. <ph type="x-smartling-placeholder">

来源

Sec-Fetch-Dest 会公开请求的目的地(例如,如果 scriptimg 标记导致浏览器请求资源)。

如何使用提取元数据来防范跨源攻击

这些请求标头提供的额外信息非常简单,但借助这些额外上下文,您只需几行代码即可在服务器端构建强大的安全逻辑(也称为资源隔离政策)。

实现资源隔离政策

资源隔离政策可防止外部网站请求您的资源。阻止此类流量可缓解常见的跨网站网络漏洞,例如 CSRF、XSSI、计时攻击和跨源信息泄露。您可为应用的所有端点启用此政策,从而允许来自您自己的应用的所有资源请求以及直接导航(通过 HTTP GET 请求)。可以选择在跨网站上下文中加载的端点(例如,使用 CORS 加载的端点)停用此逻辑。

第 1 步:允许不发送“提取元数据”的浏览器发出的请求

由于并非所有浏览器都支持提取元数据,因此您需要通过检查是否存在 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

部署资源隔离政策

  1. 安装一个类似上述代码段的模块,以记录和监控网站的行为,并确保这些限制不会影响任何合法流量。
  2. 通过豁免合法跨源端点来修复潜在的违规行为。
  3. 通过丢弃不合规的请求来强制执行该政策。

发现和修正违规问题

建议您首先在服务器端代码的报告模式下启用该政策,这样测试时不会产生附带效应。或者,您也可以在中间件或反向代理中实现此逻辑,反向代理会记录您的政策在应用于生产流量时可能产生的任何违规行为。

根据我们在 Google 推出“提取元数据资源隔离”政策的经验,大多数应用在默认情况下都与此类政策兼容,并且很少需要豁免端点以允许跨网站流量。

强制执行资源隔离政策

在确认自己的政策不会影响合法的生产流量后,您就可以执行限制了,确保其他网站无法请求您的资源,并保护您的用户免受跨网站攻击。

深入阅读