跨站点请求伪造 (CSRF) 防止
在路由器中预防CSRF攻击
关于CSRF
跨站请求伪造(CSRF)攻击利用"简单"请求来执行GraphQL 操作,基于来自不应与你的服务器通信的网站发送的请求,并依据你的CORS策略(如上所述),在GraphOS Router或Apollo Router Core。
你的路由器的CORS策略允许你控制哪些网站可以与你的服务器通信。在大多数情况下,浏览器通过发送一个预检请求在发送实际操作之前进行检查。这是一个独立的HTTP请求。与大多数HTTP请求(使用GET
或POST
方法)不同,这个请求使用了一个名为OPTIONS
的方法。浏览器发送一个带有Origin
头部的请求,以及一些以Access-Control-
开头的其他头部信息。这些头部描述了潜在不受信任的JavaScript想要发起的请求类型。你的服务器返回一个包含Access-Control-*
头部(如上所述)描述其策略的响应,浏览器使用该响应来决定是否可以发送实际请求。处理OPTIONS
预检请求永远不会实际执行GraphQL操作。
然而,在某些情况下,浏览器将 不会 发送这个预检请求。如果请求被考虑为 "简单",那么浏览器会直接发送请求而不会先发送预检请求。您的服务器的响应仍然可以包含 Access-Control-*
头部,如果这些头部指示发送请求的来源不应能访问该站点,浏览器将隐藏您的服务器响应以避免有问题的JavaScript代码。
遗憾的是,这意味着您的服务器可能会执行不应允许与您的服务器通信的站点发送的 "简单" 请求中的 GraphQL 操作。这些请求甚至可以包含cookies。尽管浏览器会隐藏您的服务器响应数据以避免恶意代码,但这可能是不够的。如果运行操作有副作用,那么攻击者可能不会关心是否能够读取响应,只要它可以使用不知情的用户的浏览器(和cookies)来触发这些副作用。即使在只读 查询 中,恶意代码也可能能够根据查询的执行时间来确定有关响应的一些信息。
除了CSRF攻击外,测量简单请求时间的攻击被称为 "跨站搜索" 攻击,或 XS-Search。
预防CSRF
为了避免CSRF和XS-Search攻击, GraphQL服务器 应拒绝执行来自未对此操作进行 "预检" 的浏览器的任何操作。没有可靠的方法来检测请求是否来自浏览器,因此GraphQL服务器不应在 "简单请求" 中执行任何操作。
请求是否为 "简单" 的最重要规则是,它是否尝试设置任意的HTTP请求头。任何设置了 Content-Type
头为 application/json
(或除三个特定的值以外的任何内容)都不能是简单请求,因此它必须进行预检。因为所有 POST
请求必须由 Apollo Server 识别并包含一个指定 application/json
的 Content-Type
头,因此我们可以确信这些不是简单请求,并且如果它们来自浏览器,则它们已被预检。
但是,路由器还处理不需要 Content-Type
头的 GET
请求,因此它们可能是简单请求。那么我们如何确保只执行不是简单请求的 GET
请求?如果我们要求请求包含一个浏览器永远不会自动设置的自定义HTTP头,那么这就可以了:设置不在规范中定义的那一小部分之外的其他HTTP头的请求必须进行预检。
在路由器上配置CSRF预防
启用CSRF预防
路由器 默认启用 CSRF预防功能。当此功能启用时,路由器 仅在以下至少一个条件为真时执行GraphQL 操作:
入站请求包含一个
Content-Type
头部,指定了除了text/plain
、application/x-www-form-urlencoded
或multipart/form-data
之外的类型。ⓘ 注意
应用
application/json
作为Content-Type
类型是足够的,包括任何后缀如application/json; charset=utf-8
。由于所有POST
请求都必须使用Content-Type: application/json
,这意味着所有POST
请求都将执行。并且因为所有支持GET
请求的Apollo Client Web(包括版本v0.13.0
及以上)的版本,包括其旧名称“Apollo Android”)都包含Content-Type: application/json
头部,所以任何来自Apollo Client Web(POST
或GET
)的请求也将被执行。存在一个
X-Apollo-Operation-Name
头部。此头部由Apollo iOS(v0.13.0+)和Apollo Kotlin(所有版本,包括其旧名称“Apollo Android”)的所有POST
或GET
操作发送,因此来自Apollo iOS或Apollo Kotlin的任何请求都将被执行。存在一个
Apollo-Require-Preflight
头部。
ⓘ 注意
- 所有HTTP头部名称不区分大小写。
- 与执行GraphQL操作的请求不同,CSRF预防不应用于着陆页或健康检查。然而,它适用于插件添加的新端点。
不满足上述条件之一的HTTP请求将使用状态码400
被拒绝,并会有一条明确说明需要添加哪些头部以使请求成功的消息。
这不应影响您graph的合法使用,除非您有发送GET
请求且不是Apollo Client Web、Apollo iOS或Apollo Kotlin的客户。如果您使用其他客户发送GET
请求,您应将它们配置为与所有请求一起发送非空的Apollo-Require-Preflight
头部。
您还可以配置允许执行的头部集合。例如,如果您使用一个 GraphQL 客户端 执行 GET
请求,不发送 Content-Type
、X-Apollo-Operation-Name
或 Apollo-Require-Preflight
头部,但发送一个 Some-Special-Header
头部:
csrf:required_headers:- X-Apollo-Operation-Name- Apollo-Require-Preflight- Some-Special-Header
对 Content-Type
的检查保持不变。
禁用 CSRF 防护
⚠️ 注意
我们强烈建议您在 router 中保持 CSRF 防护开启。
在某些情况下,例如仅包含公共数据的 GraphQL 服务器 允许无 mutation,或在浏览器无法访问不受信任网站的保护私有网络中,禁用 router 中的 CSRF 可能是合适的。
要禁用 router 中的 CSRF 防护,请将 csrf.unsafe_disabled
设置为 true
:
csrf:unsafe_disabled: true