CSRF第N次学习——搞清楚攻击为什么能成功
是非审之于己,毁誉听之于人,得失安之于数
背景
最近在看微服务认证及鉴权的设计方案,在了解了token的相关存储及技术之后(session、cookie、jwt等),发现token安全也是非常关键的一个方面。本以为对于XSS、CSRF这种常见安全问题已经足够熟悉,结果发现依然是一知半解,完全是没有理解通透。故在此做个总结。
CSRF介绍
攻击流程
解释
1.用户C通过浏览器登陆并浏览信任网站A;
2.在用户C的浏览器中生成网站A的Cookie;
3.用户C通过浏览器访问恶意网站B;
4.在恶意网站B的一些HTML标签(一张图片)或者js中有一些代码,让用户C去访问网站A(发送用户C对网站A的request),进行一些恶意操作
5.此时,这个request请求会携带网站A的Cookie去请求网站A;
网站A无法识别请求是否是用户的请求,还是恶意网站盗用身份后的请求,所以会按照请求内容进行操作。
疑问点
疑问点1:攻击者做了什么,究竟是如何攻击成功的?
1.攻击者提供了恶意网站B,攻击者制作的恶意网站一般是针对网站A精心制作的(攻击者知道网站A有这样的漏洞,进而制作成的网站B),所以用户基本上都是无感知且攻击大概率能成功的。
2.正常访问网站B后,在网站B的用户页面之下,存在恶意访问网站A的连接(某个接口),一旦用户C点击或访问对应的按钮或页面,就会触发执行访问网站A的动作(这个过程用户C一般无感知)
3.在执行访问网站A的动作时,因为网站A已经登录,浏览器会默认把存储的cookie一起发送到网站A,网站A识别用户的依据就是通过cookie里的sessionid或其他标识,所以就认为是用户C在进行操作,故而攻击成功。
疑问点2:攻击者是否拿到了用户的cookie信息?
否,攻击者在整个攻击过程中,没有参与任何操作,一切都是用户自己操作的。攻击者(或者说恶意网站B)也没有获取用户C的Cookie 信息,攻击者拿不到(如果设置了httponly)也不需要拿,用户C访问网站B时会恶意执行访问网站A的动作,浏览器便会将cookie信息附带给网站A。
修复方案
验证http referer字段
1.根据HTTP协议,在HTTP头中有一个字段叫Referer,它记录了该HTTP请求的来源地址。在通常情况下,访问一个安全受限页面的请求必须来自于同一个网站,所以一旦校验了referer,上述的攻击便不能成功。
2.需要注意的是:
- 根据标准或要求,一般来说浏览器的referer内容是无法改变的,但是也无法完全保证所有浏览器更改不了(虽然HTTP协议上有明确的要求,但是每个浏览器对于Referer的具体实现可能有差别,并不能保证浏览器自身没有安全漏洞。使用验证 Referer 值的方法,就是把安全性都依赖于第三方(即浏览器)来保障,从理论上来讲,这样并不是很安全。在部分情况下,攻击者可以隐藏,甚至修改自己请求的Referer。)
- 当一个请求是页面请求(比如网站的主页),而来源是搜索引擎的链接(例如百度的搜索结果),也会被当成疑似CSRF攻击,从而导致正常的业务失败。所以在判断的时候需要过滤掉页面请求情况(需要保证所有的GET请求没有业务操作)
- 只能防范第三方域名及域外的攻击,不能排除本域发起CSRF攻击。攻击者有权限在本域发布评论(含链接、图片等),那么它可以直接在本域发起攻击,这种情况下同源策略无法达到防护的作用。
CSRF Token
1.通常来说,这是最有效的防止CSRF攻击的方案。CSRF攻击之所以能够成功,是因为服务器误把攻击者发送的请求当成了用户自己的请求。那么我们可以要求所有的用户请求都携带一个CSRF攻击者无法获取到的csrftoken。服务器通过校验请求是否携带正确的csrftoken,来把正常的请求和攻击的请求区分开,即可以防范CSRF的攻击。
2.具体实现上,存在一写细节,具体可以参考: 前端安全系列(二):如何防止CSRF攻击
3.Token是一个比较有效的CSRF防护方法,只要页面没有XSS漏洞泄露Token,那么接口的CSRF攻击就无法成功。
疑问点:csrftoken应该放在哪里?
1.一直有一个疑问是,既然攻击是自动附带cookie从而导致攻击成功的,那么,csrftoken还能放在cookie中吗,是可以的。我们需要理解CSRF Token机制成功的要素是不可预知【从服务端csrftoken校验比较角度预防】,且csrftoken是通过参数(如请求体、请求头)的形式发送的,所以只要保证csrftoken无法预知或被盗取就行(攻击方无法通过参数发送正确的csrftoken,因为无法预知)
2.通俗的讲,服务器进行csrf防御校验的时候,是拿用户http请求体中的token参数值和cookie中的csrftoken值进行比对(也叫双重cookie验证)。如果值一样了,操作才被允许执行。所以放在cookie是可以的,如果放在session,则需要到服务端先根据用户查找对应的csrftoken,再进行比较,会比较占用服务端内存,所以这也正是一些框架如restframework,spring会把csrftoken放在cookie里的原因。
3.还有就是,通过cookie中的samesite属性的设置【从cookie自动附带发送成功与否角度预防】,当设置为 Strict 或 Lax时,能够直接避免cookie中的csrftoken被浏览器自动附带传到服务端(服务端校验时如果拿不到csrftoken直接检验拒接请求就好了),这样csrftoken也可以放在cookie中。不过samesite并不是专门为放在cookie里的csrftoken设计的,而且确实有些冗余(多一层防御),正如上面所说,服务端校验参数和cookie的csrftoken就好,samesite是一套从源头防止CSRF的方案(该方案依然存在缺陷,如不支持子域、各浏览器兼容性不好等)下面有简要描述。
在http头中自定义属性并验证
自定义属性的方法也是使用token并进行验证,和前一种方法的不同的是,不是把token以参数的形式置于http请求中,而是把它放在http头中自定义的属性中。通过XMLHttpRequest这个类,可以一次性给所有该类请求加上csrftoken这个http头属性,并把token放入其中。这样解决了前一种方法在请求中加入token的不便,同时,通过这个类请求的地址不会被记录到浏览器的地址栏,也不用担心token会通过referer泄露网站地址。
验证码
1.针对一些关键的业务操作,需要验证码进行校验,基本上可以完全避免CSRF攻击。如银行转账或支付的时候都要求输入密码或者验证码。
2.该方案不足的地方在于比较影响用户的使用体验。所以,只有对于一些私密,以及较为重要的操作,才尽量使用二次安全验证,比如短信,以及动态口令,或者是验证码的安全效验。
Samesite
1.防止CSRF攻击的办法已经有上面的预防措施。为了从源头上解决这个问题,Google起草了一份草案来改进HTTP协议,那就是为Set-Cookie响应头新增Samesite属性,它用来标明这个 Cookie是个“同站 Cookie”,同站Cookie只能作为第一方Cookie,不能作为第三方Cookie,Samesite 有两个属性值,分别是 Strict 和 Lax,只要设置了Samesite为 Strict 或 Lax就行,设置为None则无用。具体: 前端安全系列(二):如何防止CSRF攻击
2.需要注意的是,Samesite目前还并不成熟,Safari等部分浏览器并不支持。
3.Samesite不支持子域。例如,种在topic.a.com下的Cookie,并不能使用a.com下种植的SamesiteCookie。这就导致了当我们网站有多个子域名时,不能使用SamesiteCookie在主域名存储用户登录信息。每个子域名都需要用户重新登录一次。
参考
1.前端安全系列(二):如何防止CSRF攻击(可以多看几遍):https://tech.meituan.com/2018/10/11/fe-security-csrf.html
2.什么是CSRF?为什么CSRF Token写在COOKIE里面:https://blog.csdn.net/qq_41517936/article/details/107072687
3.为什么CSRF Token能够放在Cookie中:https://blog.csdn.net/a709046532/article/details/119999318