跨站请求伪造
跨站请求伪造(CSRF)是一种混淆代理攻击,攻击者利用用户cookie或网络位置的环境权限,诱使浏览器向目标发送请求。例如,attacker.example可以向受害者提供以下HTML:
|
|
浏览器将使用受害者的cookie向https://example.com/send-money发送POST请求。
基本上所有使用cookie进行身份验证的应用程序都需要防范CSRF。重要的是,这不是为了防止攻击者发出任意请求(因为攻击者不知道用户的cookie),而是与浏览器合作识别来自不受信任源的认证请求。
与跨源资源共享(CORS)不同,CORS是关于跨源共享响应,而CSRF是关于接受状态变更请求,即使攻击者看不到响应。防止泄漏要复杂和微妙得多,特别是在Spectre时代。
浏览器为什么最初允许这些请求?与Web平台中的任何东西一样,主要是出于遗留原因:过去就是这样工作的,改变它会破坏现有功能。重要的是,禁用这些第三方cookie会破坏重要的单点登录(SSO)流程。所有CSRF解决方案都需要为这些罕见例外提供绕过机制。
同站 vs 同站 vs 同源
要防范CSRF,首先需要定义什么是跨站或跨源请求,以及哪些应该被允许。
https://app.example.com、https://marketing.example.com,甚至http://app.example.com(取决于定义)都是同站但不同源。
很容易将目标声明为确保请求来自同一站点,但同一站点中的不同源实际上可能处于非常不同的信任级别:例如,在旧的营销博客中获取XSS可能比在管理面板中容易得多。
然而,信任度最显著的差异是在HTTPS和HTTP源之间,因为网络攻击者可以在后者上提供任何它想要的内容。这有时被称为MitM CSRF绕过,但实际上它只是无方案同站跨源CSRF攻击的一个特例。
Web平台的某些部分应用了有方案的同站定义,其中https://app.example.com和http://app.example.com不是同站:
- 通常cookie应用无方案定义(HTTP = HTTPS)。有一个解决这个问题的提案,Origin-Bound-Cookies,但尚未发布。
- SameSite cookie属性过去应用无方案定义(HTTP = HTTPS)。Chrome在2020年通过Schemeful Same-Site改变了这一点,但Firefox和Safari从未实现它。
- Sec-Fetch-Site(以及一般的HTML和Fetch规范)应用有方案定义(HTTP ≠ HTTPS)。
如果可能,使用HTTP严格传输安全(HSTS)是缓解HTTP→HTTPS问题的潜在方法。
对策
有多种潜在的CSRF对策,其中一些仅在几年前才可用。
双重提交或同步令牌
“经典"对策是CSRF令牌,一个在请求中提交的大随机值(例如作为隐藏的<input>),并与存储在cookie中的值(双重提交)或有状态的服务器端会话中的值(同步令牌)进行比较。
通常,双重提交不是同源对策,因为同站源可以通过"cookie tossing"相互设置cookie。这可以通过__Host- cookie前缀或通过使用签名元数据将令牌绑定到会话来缓解。前者使攻击者无法设置cookie,后者确保攻击者不知道要设置的有效值。
请注意,对cookie或令牌进行签名是不必要且无效的,除非它将令牌绑定到用户:否则,进行cookie tossing的攻击者可以通过自己登录网站获得有效的签名对,然后将其用于攻击。
这种对策将跨源伪造问题转变为跨源泄漏问题:如果攻击者可以从跨源响应获取令牌,它可以伪造有效请求。
HTML正文中的令牌应被屏蔽,作为对BREACH压缩攻击的对策。
CSRF令牌的主要问题是它们要求开发人员检测所有表单和其他POST请求。
Origin头
浏览器在Origin头中发送请求的源,因此可以通过拒绝来自其他源的非安全请求来缓解CSRF。
主要问题是知道应用程序自身的源。一个选项显然是要求开发人员配置它,但这会产生摩擦,并且可能并不总是容易(例如对于开源项目和代理设置)。
最接近的应用程序自身源的近似值是Host头。这有两个问题:
- 如果涉及反向代理,它可能与浏览器源不同;
- 它不包括方案,因此无法知道http:// Origin是跨源HTTP→HTTPS请求还是同源HTTP请求。
一些较旧(2020年前)的浏览器没有为POST请求发送Origin头。
在各种情况下,该值可能为null,例如由于Referrer-Policy: no-referrer或跟随跨源重定向。null必须被视为跨源请求的指示。
一些隐私扩展会移除Origin头而不是将其设置为null。这应被视为扩展引入的安全漏洞,因为它移除了浏览器跨源请求的任何可靠指示。
SameSite Cookie
如果认证cookie明确设置为具有Lax或Strict的SameSite属性,它们将不会随非安全跨站请求发送。
根据设计,这不是跨源保护,并且无法通过__Host-前缀(或Secure属性)修复,因为这是关于谁可以设置和读取cookie,而不是关于请求源自何处。同站HTTP源的风险在未实现Schemeful Same-Site的浏览器中仍然存在。
请注意,由于广泛的破坏,特别是SSO流程中的破坏,默认SameSite Lax的推出大多失败。一些浏览器现在默认使用Lax-allowing-unsafe,而其他浏览器在cookie设置后的前两分钟默认使用None。这些默认值不是有效的CSRF对策。
非简单请求
尽管CORS不是设计用于防范CSRF,但"非简单请求”(例如设置简单<form>无法设置的标头)会通过OPTIONS请求进行预检。
应用程序可以选择仅允许非简单请求,但这相当有限,因为"简单请求"包括<form>产生的所有请求。
获取元数据
为了向网站提供可靠的跨源信号,浏览器引入了获取元数据。特别是,Sec-Fetch-Site头设置为cross-site/same-site/same-origin/none,现在是缓解CSRF的推荐方法。
该头自2023年起在所有主要浏览器中可用(除Safari外更早)。
一个限制是它仅发送到"可信源",即HTTPS和localhost。请注意,这不是关于发起者源的方案,而是关于目标的方案,因此它为HTTP→HTTPS请求发送,但不为HTTPS→HTTP或HTTP→HTTP请求发送(localhost→localhost除外)。如果缺少Sec-Fetch-Site,对Origin=Host的宽松回退是一个选项,因为HTTP→HTTPS请求不是问题。
2025年防范CSRF
总之,要防范CSRF,应用程序(或者更确切地说,库和框架)应拒绝跨源非安全浏览器请求。最开发者友好的方法是主要使用获取元数据,这不需要额外的检测或配置。
-
允许所有GET、HEAD或OPTIONS请求。 这些是安全方法,并且已经在堆栈的各层假定不改变状态。
-
如果Origin头匹配受信任源的允许列表,允许请求。 受信任源应配置为完整源(例如https://example.com),并通过简单相等性与头值进行比较。
-
如果存在Sec-Fetch-Site头:
- 如果其值为same-origin或none,允许请求;
- 否则,拒绝请求。 这保护了所有主要的最新浏览器,用于托管在可信(HTTPS或localhost)源上的站点。
-
如果Sec-Fetch-Site和Origin头都不存在,允许请求。 这些请求不是来自(2020年后)浏览器,并且不受CSRF影响。
-
如果Origin头的主机(包括端口)匹配Host头,允许请求,否则拒绝。 这要么是到HTTP源的请求,要么是由过时浏览器发出的。
此算法的唯一误报(不必要的阻塞)是到非可信(纯HTTP)源的请求,这些请求通过更改Host头的反向代理。可以通过将源添加到允许列表来解决此边缘情况。
在现代浏览器中没有误报,但2023年前的浏览器将容易受到HTTP→HTTPS请求的攻击,因为Origin回退是方案无关的。HSTS可用于缓解这种情况(在2020年后的浏览器中),但请注意,过时的浏览器可能有更紧迫的安全问题。
最后,应该有一个严格范围的绕过机制,例如用于SSO边缘情况,并带有适当的安全标志。例如,它可以是基于路由的,或者要求在CSRF中间件之前手动标记请求。
Go 1.25在net/http中引入了CrossOriginProtection中间件,它实现了此算法。