CSRF令牌为什么要通过HTTP头部而不是cookie来验证

首先介绍一下CSRF的验证过程:
client发送get请求 -> 服务生成XSRF-TOKEN,存入用户session中。返回响应,响应包含XSRF-TOKEN cookie -> 浏览器接收响应后自动保存cookie -> js获取此cookie的值,构造X-XSRF-TOKEN头部 -> 向server发送post请求 -> server通过client传递过来的包含server_id的cookie反序列化此用户的session数据 -> 从session中拿出用户的CSRF-TOKEN与请求头中的值进行对比 -> 验证通过或失败

然后想象这么一种场景。有一个正常域名A被XSS注入,其中的恶意代码如下:


这时,当你打开此页面后,浏览器会默认发起http请求,请求地址就是src中的地址。因为是浏览器发出的请求,所以这个请求默认会带上此域名的cookies。如果恰好你在bank.example.com这个域名是登陆状态,那么就会中招了。

这里重点强调一下cookie的机制:浏览器向一个域名发起http请求时会带上浏览器保存的关于那个域名的cookies,而不管你从哪个网站发请求。上面的例子,请求就是从域名A发出的。

清楚了上面的攻击手段,对于CSRF验证令牌为什么放在头部这个问题就很好理解了。为了添加新的头部,js就必须获取XSRF-TOKEN的值,然后构造X-XSRF-TOKEN头部。而对于上面那种攻击手段,是没有能力获取cookie的。

那么问题又来了,既然域名A都已经被入侵了,那攻击者完全可以植入js代码,然后获取cookie,构造头部,这一系列操作不也是轻而易举的吗?

答案是,不行。因为js要获取cookie存在三个因素的限制:

  • Path。也就是URI的路径,路径不同,不允许访问
  • Doamin。域名不同不允许访问
  • http-only。设置为true之后,js不能访问

很显然,因为当前发起请求所在的域名和攻击者的目标域名不一样,所以即使页面被注入的js代码,也无法获取目标域名的cookie。其次,对于存放session_id的cookie,一般都设置为http-only,禁止js读取,这也是防止xss攻击的主要手段。

综上所述,对于存放CSRF令牌的cookie应该取消http-only的设置,同时session_idcookie默认要启用http-only属性,此时后端采用通过头部验证令牌的方式才能全面的防范CSRF攻击。

备注:

  1. axios在发送POST请求时,如果当前域名存在XSRF-TOKENcookie,它自动会构造好X-XSRF-TOKEN头部。
  2. 关于XSRF-TOKENsession_id这些cookie的名字,不同的框架可能会有所不同,但是功能都是一样的。

完!

参考资料
MDN - Cookies
laravel - CSRF Protection
laravel的CSRF防护机制和延伸

你可能感兴趣的:(CSRF令牌为什么要通过HTTP头部而不是cookie来验证)