CSRF同学的那些事~

前言

上回聊了XSS同学,今天来聊聊CSRF同学~

相比起XSS,貌似CSRF并不常听到,但其实CSRF一样具有严重的破坏性。

一、什么是CSRF攻击

CSRF攻击,英文全称是Cross—Site Request Forgery,其意思就是跨站请求伪造,跟XSS一样,存在巨大的危害性。它的本质就是:攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。

一个典型的CSRF攻击有如下过程(假设当前有用户C,网站A,恶意网站B):

  1. 用户C打开浏览器,访问受信任的网站A,并输入了账号密码进行登录;
  2. 在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户C登录网站A成功,可以正常的发送请求到网站A;
  3. 用户C在未退出网站A之前,在同一浏览器中,受攻击者引诱去访问了恶意网站B;
  4. 恶意网站B接收到用户C的请求后,返回了一些攻击性代码,并发出了一个恶意请求要求请求网站A;
  5. 浏览器接收到该恶意请求后,在用户C不知情的情况下,默认携带着用户C的Cookie信息,向网站A发出请求。网站A并不知道该请求其实是恶意网站B发起的,所以会根据用户C的Cookie信息以用户C的权限去处理该请求,从而导致来自网站B的恶意代码被执行;
  6. 攻击完成,攻击者在受害者C不知情的情况下,冒充了受害者C,让网站A执行了自己所定义的操作。

举个例子:
假设现在有受害者小A,黑客小B;
小A在银行有一笔钱,可以通过银行的网站发送请求http://bank.example/withdraw?account=A&amount=10000&for=C 就可以使A把10000块钱转到C的账户下。通常情况下,该请求发送到网站后,服务器会进行验证,而此时小A已经是成功登录的;

恰好,黑客小B在该银行也有自己的账户,他知道通过上述的URL请求可以进行转账。他就想,要是我也发送个请求给银行http://bank.example/withdraw?account=A&amount=10000&for=B,岂不是能将别人的钱转我的账户下,想想就觉得开心呀~ 可是这个请求来自小B自己,而不是其他用户主动发送的,肯定是不能通过安全认证的,因此该请求不会起作用;

这时,黑客小B就就想到了使用CSRF的攻击方式,他先自己做了一个网站,在网站中放入如下代码:src="http://bank.example/withdraw?account=A&amount=10000&for=B",并且通过广告等方式引诱受害人小A来访问他的网站。当小A访问了该网站时,上述的URL就会从小A的浏览器中发向银行,而这个请求会附带小A浏览器中的Cookie一起发向银行服务器。大多数情况下,该请求会失败,因为服务器需要小A的认证信息。但是,如果小A当时恰好刚访问这个银行网站不久,他的浏览器与银行之间的session尚未过期,浏览器的Cookie中就会含有小A的认证信息。这时,悲剧就发生了,这个URL请求就会得到响应,钱将从小A的账户转移到黑客小B的账户中,而小A时毫不知情的。等之后小A发现账户的钱少了,即使他去银行查询转账记录,也只能查询到确实有一条来自于他本人的合法请求转移了资金,没有任何被攻击的痕迹。而黑客小B则拿着钱逍遥法外了。

通过上述例子,我相信你已经对CSRF攻击有了大致的了解了。那CSRF攻击都有哪些攻击类型呢?

二、CSRF常见攻击类型

1. GET类型的CSRF

GET类型的CSRF利用比较简单,只需要一个HTTP请求,就向上述的例子一样,只要受害了访问了含有恶意代码的网站之后,浏览器就会带着自己的Cookie等信息,向恶意代码中的URL地址发出HTTP请求,从而受到攻击。

2. POST类型的CSRF

POST类型的CSRF利用起来通常使用的是一个自动提交的表单,如:

<form action="http://bank.example/withdraw" method=POST>
    <input type="hidden" name="account" value="A" />
    <input type="hidden" name="amount" value="10000" />
    <input type="hidden" name="for" value="B" />
</form>
<script> document.forms[0].submit(); </script> 

当访问了该页面后,表单会自动提交,相当于模拟用户完成了一次POST请求。

3. 链接类型的CSRF

链接类型的CSRF需要用户点击链接才会触发。这种类型通常是在论坛中发布的图片中嵌入恶意链接,或者以广告的形式诱导用户中招,攻击者通常会以比较夸张的词语诱骗用户点击,例如:

 <a href="http://bank.example/withdraw?account=A&amount=10000&for=B" taget="_blank">
  重磅消息!!!
  <a/>

由于之前用户登录了信任的网站,并且保存了登录状态,只要用户主动点击上面的链接,则会造成被攻击。

三、CSRF防护策略

通过以上的了解,我们可知道,CSRF攻击通常有一下特点:

  1. CSRF通常发生在第三方域名;
  2. CSRF攻击者不能获取到用户的Cookie等信息,只是“冒用”

根据上述特点,可制定不同的防护策略:

  • 阻止不明外域访问
  1. 同源检测
  2. Samesite Cookie
  • 提交时要求附加本域才能获取的信息
  1. CSRF Token
  2. 双重Cookie验证

接下来,我们对这几种策略做详细的说明。

1. 同源检测

我们知道了CSRF大多来自第三方的网站,那么既然如此,我们就直接禁止外域(不受信任的域名)对我们发起请求。

  1. 那么问题来了,我们该如何去判断请求是否来自外域呢?

这就可以利用Origin Header和Referer Header;那这两个header是什么东西呢?在HTTP协议中,每一个异步请求都会携带两个header,用于标记来源域名。这两个header在浏览器发起请求时,大多数情况会自动带上,并且不能由前端自定义内容。服务器可以通过解析这两个header中的域名,确定请求的来源域。就以上述例子来说,如果来源域是以 bank.example 开头的域名,则说明该请求是来自银行网站自己的请求,是合法的。如果来源域是其他网站的话,则有可能是黑客的 CSRF 攻击,拒绝该请求。

  1. 这时问题又来了,当Origin和Referer头文件都不存在时该怎么办?

如果Origin和Referer都不存在的话,建议直接进行阻止。因为Origin在某些情况下是不存在的,比如:IE11的同源策略,302重定向等;而Referer的话,在某些浏览器上,已经有一些方法是可以篡改或隐藏Referer值的。如果刚好银行网站支持某个浏览器,而该浏览器又同时有方法可以隐藏Referer值以及不存在Origin的情况,那此时没有阻止的话,攻击者同样可以发起CSRF攻击。同样,还是前面的情况,如果黑客直接将Referer值篡改为以 bank.example 域名开头的地址,这样还是可以通过验证,从而进行 CSRF 攻击。

通过上述内容,我们可以知道,同源检测是一个相对简单的防范方法,它能够防范绝大多数的CSRF攻击,但它并不是绝对的安全。即便是使用最新的浏览器,无法篡改Referer值,同源检测仍然有些小问题,因为 Referer 值会记录下用户的访问来源,有些用户认为这样会侵犯到他们自己的隐私权,特别是有些组织担心 Referer 值会把组织内网中的某些信息泄露到外网中。因此,用户自己可以设置浏览器使其在发送请求时不再提供 Referer。当他们正常访问银行网站时,在Origin不存在的情况下,网站会因为请求没有 Referer 值而认为是 CSRF 攻击,拒绝合法用户的访问。所以对于安全性较高的网站,我们就需要对关键的接口做一些额外的防护了。

2. Samesite Cookie

在Set-Cookie响应头中新增了Samesite属性,它是用来限制第三方Cookie的,从而减少安全风险。那什么叫做第三方Cookie呢?这种由第三方网站引导发出的 Cookie,就称为第三方 Cookie。它实际作用就是用来标明这个 cookie 是个“同站 cookie”,“同站 cookie” 只能作为第一方cookie,不能作为第三方cookie,因此可以限制第三方Cookie。

  • 它可以设置两个属性值来预防,分别是Strict,Lax;
  1. Samesite=Strict
    这个规则最为严格,称为严格模式,完全禁止了第三方Cookie,跨站点时,任何情况都不会发送Cookie。也就是说,只有当前网页的 URL 与请求目标一致,才会带上 Cookie。不过这个规则过于严格,可能会造成非常不好的用户体验。比如,当前网页有一个 GitHub 链接,用户点击跳转就不会带有 GitHub 的 Cookie,跳转过去总是未登陆状态。

  2. Samesite=Lax
    这个规则就稍微宽松一些,称为宽松模式,大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外。什么意思呢?

举个例子:
当前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也不会发送。

导航到目标网址的 GET 请求,只包括三种情况:链接,预加载请求,GET 表单。详见下表:

请求类型示例正常情况Lax
链接<a href="…">发送 Cookie发送 Cookie
预加载<link rel=“prerender” href="…"/>发送 Cookie发送 Cookie
GET 表单<form method=“GET” action="…">发送 Cookie发送 Cookie
POST 表单<form method=“POST” action="…">发送 Cookie不发送
iframe<iframe src="…">发送 Cookie不发送
AJAX$.get("…")发送 Cookie不发送
Image<img src="…">发送 Cookie不发送

设置了Strict或Lax以后,基本就杜绝了 CSRF 攻击。当然,前提是用户浏览器支持 SameSite 属性。目前谷歌已将Lax作为默认设置。不过目前也并不是所有浏览器都支持该属性,而且在平时应用中,它也有许多的缺点,比如不支持子域等等,这就造成了很多不好的体验。因此其应用场景有待观望。

3. CSRF Token

通过前面我们知道,CSRF是 “冒用” 用户信息来发起攻击的。那么,由于攻击者"冒用了"用户信息,所以服务器无法识别是否真的是该用户发出的请求,就误把攻击者发送的请求当成了真实用户的请求给执行了。那如果,我们在初始化页面时,给双方一个"暗号",之后的每次请求中,都附带上这个暗号,那么服务器对接收到的请求,会验证咱们之间的暗号是否正确,正确的话就代表验证通过。否则,验证失败,拒绝请求。而这个暗号就是Token

CSRF Token的防护策略可分为三个步骤:

  1. 首先,当用户打开页面时,服务器需要给该用户生成一个Token(该Token一般是经过加密之后的,一般包含随机字符串和时间戳等),之后在每次页面加载时,使用JS遍历整个DOM树,对于DOM中所有的a和form标签后加入Token。这样可以解决大部分的请求,但是对于在页面加载之后动态生成的HTML代码,这种方法就没有作用,还需要程序员在编码时手动添加Token。
  2. 页面提交请求时,附带上该Token
    对于GET请求的话,直接在请求的URL地址后面加上,而对于POST的话,要在from的最后附加上一个type为hidden属性的input框,将Token附带value上,这样就可以把Token以参数的形式加入请求了。
  3. 服务器验证Token的正确性
    当服务器拿到该Token之后,对其进行验证,如果内容正确,且时间未过期,则验证通过。否则验证失败。

这个方法虽然比同源检测安全一些,但是需要在每一个页面和接口都添加对应的输出和校验。这种方法工作量巨大,且有可能遗漏。

4. 双重Cookie验证

这个方法利用CSRF攻击不能获取到用户Cookie的特点,可以要求在每次请求中携带一个Cookie中的值进行验证。

一般采用如下流程:

  1. 在用户访问网站时,向请求的域名注入一个Cookie,内容为随机字符串(例如:csrfcookie=“g3h12k3b21kjh32jk”);
  2. 在前端向后端发起请求时,取出该cookie值,并附带到请求中(如:http://xxxx.com/comment?csrfcookie=“g3h12k3b21kjh32jk”);
  3. 后端接口验证Cookie的字段与URL参数中的字段是否一致,一致则验证通过,不一致则拒绝请求。

这种方法有好处也有坏处。好处在于它更方便,简单了,使用成本也比较低。坏处就在于它会给Cookie增加额外的字段,而且如果页面上有XSS等漏洞,那么攻击者就可以发起攻击,对Cookie进行修改,之后再使用自己配置的Cookie来进行CSRF攻击,那么此时这种防御方式就失效了。

总结

要更好的防御CSRF,还得需要我们自己结合实际综合考虑,这样才能更好的预防CSRF的发生。还有,在我们平时上网的时候,尽量不要打开可疑的链接,一定要打开时,可以使用不常用的浏览器进行查看,以免自己遭受攻击~~~

参考资料:https://tech.meituan.com/2018/10/11/fe-security-csrf.html

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值