为什么CSRF Token能够放在Cookie中
0x01 前言
在某次测试中注意到网站的CSRF的TOKEN放在Cookie中,感觉和之前的一些知识起了冲突。遂查了些资料整理明白了,并记录于此文。
0x02 CSRF与CSRF Token
相信有很多师傅的知识都是通过道哥的《白帽子讲WEB安全》中习得的,里面有一个删除搜狐博客的例子,从大体来说可以概括如下:
对于防御方法,很多博文中也有介绍,就是通过种种形式添加CSRF Token
0x03 疑问&解答
然后呢在某次测试中,抓到的包如下
可以看到有多个csrftoken的字样,当时我就纳闷了,因为攻击者的CSRF攻击(大多数)恰恰就是利用的Cookie,那么csrftoken写在这个地方有用吗?
为了验证我的想法,我用Burp的CSRF工具进行了一个小小的测试:
在界面点击按钮,可以看到发起了请求
之后看到拿到的数据包,可以看到是没有Cookie信息的,理所当然不能通过网站的校验,自然网站本身也是安全的。
那这个我之前学到的知识冲突在哪呢?我们常见的CSRF Token往往是放在URL或者Form表单中的,没太意识到原来CSRF Token也能放在Cookie中,那么这种方式能够防御的原理是啥?
在一番查阅资料后,我发现主要使用了两种技术,能够使得站点能够把Token放在Cookie中,参考链接
其一是Chrome中Cookie的SameSite字段,这里摘抄一段,如下
防止CSRF攻击的办法已经有上面的预防措施。为了从源头上解决这个问题,Google起草了一份草案来改进HTTP协议,那就是为Set-Cookie响应头新增Samesite属性,它用来标明这个 Cookie是个“同站 Cookie”,同站Cookie只能作为第一方Cookie,不能作为第三方Cookie,Samesite 有两个属性值,分别是 Strict 和 Lax,下面分别讲解:
Samesite=Strict
这种称为严格模式,表明这个 Cookie 在任何情况下都不可能作为第三方 Cookie,绝无例外。比如说 b.com 设置了如下 Cookie:
Set-Cookie: foo=1; Samesite=Strict
Set-Cookie: bar=2; Samesite=Lax
Set-Cookie: baz=3
- 1
- 2
- 3
我们在 a.com 下发起对 b.com 的任意请求,foo 这个 Cookie 都不会被包含在 Cookie 请求头中,但 bar 会。举个实际的例子就是,假如淘宝网站用来识别用户登录与否的 Cookie 被设置成了 Samesite=Strict,那么用户从百度搜索页面甚至天猫页面的链接点击进入淘宝后,淘宝都不会是登录状态,因为淘宝的服务器不会接受到那个 Cookie,其它网站发起的对淘宝的任意请求都不会带上那个 Cookie。
Samesite=Lax
这种称为宽松模式,比 Strict 放宽了点限制:假如这个请求是这种请求(改变了当前页面或者打开了新页面)且同时是个GET请求,则这个Cookie可以作为第三方Cookie。比如说 b.com设置了如下Cookie:
Set-Cookie: foo=1; Samesite=Strict
Set-Cookie: bar=2; Samesite=Lax
Set-Cookie: baz=3
当用户从 a.com 点击链接进入 b.com 时,foo 这个 Cookie 不会被包含在 Cookie 请求头中,但 bar 和 baz 会,也就是说用户在不同网站之间通过链接跳转是不受影响了。但假如这个请求是从 a.com 发起的对 b.com 的异步请求,或者页面跳转是通过表单的 post 提交触发的,则bar也不会发送。
而在我们的案例中,打开开发者界面,可以看到我们的Cookie是被设置了SameSite属性的,且其值为Lax。我们的数据包是个POST请求,自然也就不会带上Cookie了
为了进一步验证,我们可以将SameSite属性设置成None,同时添加Secure属性,如下
再次使用CSRF的Poc,可以发现可以携带Cookie了
我们需要注意的是,这个SameSite属性是Google家的东西,也就是可能存在别的浏览器不兼容的情况,所以我们还需要别的方案作辅助验证
刚好这个站点也有这个方案,名字是双重Cookie验证,这里继续摘抄
在会话中存储CSRF Token比较繁琐,而且不能在通用的拦截上统一处理所有的接口。
那么另一种防御措施是使用双重提交Cookie。利用CSRF攻击不能获取到用户Cookie的特点,我们可以要求Ajax和表单请求携带一个Cookie中的值。
双重Cookie采用以下流程:
- 在用户访问网站页面时,向请求域名注入一个Cookie,内容为随机字符串(例如
csrfcookie=v8g9e4ksfhw
)。 - 在前端向后端发起请求时,取出Cookie,并添加到URL的参数中(接上例
POST https://www.a.com/comment?csrfcookie=v8g9e4ksfhw
)。 - 后端接口验证Cookie中的字段与URL参数中的字段是否一致,不一致则拒绝。
此方法相对于CSRF Token就简单了许多。可以直接通过前后端拦截的的方法自动化实现。后端校验也更加方便,只需进行请求中字段的对比,而不需要再进行查询和存储Token。
当然,此方法并没有大规模应用,其在大型网站上的安全性还是没有CSRF Token高,原因我们举例进行说明。
由于任何跨域都会导致前端无法获取Cookie中的字段(包括子域名之间),于是发生了如下情况:
- 如果用户访问的网站为
www.a.com
,而后端的api域名为api.a.com
。那么在www.a.com
下,前端拿不到api.a.com
的Cookie,也就无法完成双重Cookie认证。 - 于是这个认证Cookie必须被种在
a.com
下,这样每个子域都可以访问。 - 任何一个子域都可以修改
a.com
下的Cookie。 - 某个子域名存在漏洞被XSS攻击(例如
upload.a.com
)。虽然这个子域下并没有什么值得窃取的信息。但攻击者修改了a.com
下的Cookie。 - 攻击者可以直接使用自己配置的Cookie,对XSS中招的用户再向
www.a.com
下,发起CSRF攻击。
在这个站点中,如下
也就是说,就算我们SameSite属性由于种种原因失效了,浏览器访问的时候带上了Cookie,我们依旧无法完成CSRF攻击。因为发起请求的HTTP HEADER对攻击者来说是不可控的,服务端只需要比较头部的自定义属性(即csrf-token)和Cookie中值是否一致即可