Bootstrap

CSRF Token为什么写在Cookie中?CSRF漏洞分析

1. 什么是CSRF?

CSRF或XSRF,跨站请求伪造。简单地说,就是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。这利用了web中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。

在这里插入图片描述

这里最最关键的因素是令牌。用户在网站A登陆成功后,服务端颁发令牌并存储到浏览器中。客户端存储令牌方式主要2种:Cookie、Localstorage。只有令牌存储在Cookie中才能利用CSRF攻击。
在这里插入图片描述

令牌的存储方式有两种:
第一种存储到客户端的Cookie中,存储到Cookie中大部分是基于浏览器作为客户端的业务。
第二中存储到LocalStorage中,存储到LocalStorage大部分是基于APP或者小程序内置的LocalStorage。
Cookie和LocalStorage的区别在于,用户访问同源站点的时候,Cookie会默认携带发送给服务端,而localStorage不会。就是说,客户端发送请求的时候,不需在请求中设置Cookie,请求过程会自动从客户端Cookie中获取。而令牌存储在localStorage中,客户端需要先从localstorage中获取令牌,并添加到请求头中。

2. CSRF Token的校验规则

因为同源策略的限制,当正常用户通过账号密码等方式登陆网站A后,在不注销账号或当前Cookie失效之前,再次访问网站A时(协议、IP、端口号相同则属于同源)浏览器会自动在http请求包中带上该网站用户登陆后的Cookie信息。
服务器进行CSRF防御校验的时候,是拿用户http请求体中的token参数值和Cookie中的csrftoken值进行比对。如果值一样了,操作才被允许执行。

3. HTTP复杂请求

大部分前后端分离的项目,前端都是发起复杂请求的。Content-Type:application/json,或者有自定义的Header头都属于复杂请求。
同源策略要求复杂请求必须先进行预检(OPTIONS)请求,预检通过了才能进行POST请求。预检请求会询问服务端:允许的请求源,是否允许携带Cookie,允许的请求方式(POST、PUT、DELETE等),允许的Header头字段。可以通过Nginx设置,也可以通过服务端程序设置。

4. CSRF Token为什么可以写在Cookie中?

CSRF Token写在Cookie中,攻击者不就获取到Token了,那防御不就失效了吗?
在此攻击过程中用户Cookie对于攻击者来说是不可见的是未知的、不可见的,攻击者能做到仅仅是借用Cookie,而Cookie里面具体写了什么,攻击者是不知道的。又因为Cookie里的信息对于攻击者来说是不可预知的,无法伪造的,所以将CSRF-TOKEN写在Cookie中符合就CSRF防御思想中的不可预知原则。

5. 跨域请求之简单请求和复杂请求

为了从源头上解决这个问题,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

我们在 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也不会发送。

需要注意的是,这个SameSite属性是Google家的东西,也就是可能存在别的浏览器不兼容的情况,所以还需要别的方案作辅助验证——双重Cookie验证。

6. 双重Cookie验证

利用CSRF攻击不能获取到用户Cookie的特点,我们可以要求Ajax和表单请求携带一个Cookie值。
双重Cookie验证流程:

  1. 在用户访问网站页面时,向请求域名注入一个Cookie,内容为随机字符串(例如csrfcookie=db4d8d15945941e5b48d6c1626b52eb4b)。
  2. 在前端向后端发起请求时,取出Cookie,并添加到URL的参数中(接上例POST https://www.a.com/comment?csrfcookie=db4d8d15945941e5b48d6c1626b52eb4b)。
  3. 后端接口验证Cookie中的字段与URL参数中的字段是否一致,不一致则拒绝。
;