如何防止CSRF攻击

什么是CSRF

CSRF(cross-site request forgery)跨站请求伪造:攻击者诱导受害者进去第三方网站,在第三方网站,向被攻击网站发送垮站请求,利用受害者在被攻击网站已经获取的登陆凭证。绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作。

一个典型的CSRF攻击有着如下的攻击流程:

  • 受害者登陆a.com,并保留了登陆凭证(cookie)
  • 攻击者引诱受害者访问了b.com
  • b.com向a.com发送了一个请求: a.com/act=xxx。浏览器会默认携带a.com的cookie
  • a.com接收到请求后,对请求进行验证,并确认是受害者的凭证,误认为是受害者自己发送的请求
  • a.com以受害者的名义执行了act=xxx
  • 攻击完成,攻击者在受害者不知情的情况下,冒充受害者,让a.com执行了自己定义的操作

几种常见的攻击类型

GET类型的csrf

GET类型的CSRF非常简单,只需要一个http请求即可,一般会这样利用。

https://a.com/mit-x/blog-images-bundle-2018b/ff0cdbee.example/withdraw?amount=10000&for=hacker

在受害者访问含有这个img的页面后,浏览器会自动向这个地址发出一次HTTP请求。

post类型的csrf

这种类型的csrf利用起来通常是一个自动提交的表单,如:

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

访问该页面后,表单会自动提交,相当于一次模拟post请求。
post类型的攻击通常比get要求更严格一些,但是并不复杂,任何个人网站,博客,被黑客上传的页面网站都有可能是攻击的来源,后端接口不能将安全寄托在仅允许post上面。

链接类型的csrf

链接类型的csrf不常见,比起上面两种用户打开就中招的。这种需要用户点击才会触发,这种类型通常是在论坛发布的图片中嵌入恶意链接,或者以广告的形式诱导用户中招,攻击者通常会以比较夸张的词语诱骗用户点击

 <a href="http://test.com/csrf/withdraw.php?amount=1000&for=hacker" taget="_blank">
  重磅消息!!
  <a/>

由于之前用户登陆了信任的网站A。并且保存登陆状态,只要用户主动访问上面这个页面。则表示攻击成功

CSRF的特点

  • 攻击一般发起在第三方网站,而不是被攻击的网站,被攻击的网站无法预防发生
  • 攻击者利用受害者的凭证进行请求操作,而不是直接窃取数据
  • 整个过程并不能获取受害者的登陆凭证,仅仅是冒用
  • 跨站请求可以用各种方式:图片URL、超链接、CORS、Form提交等等。部分请求方式可以直接嵌入第三方论坛、文章中,难以追踪。
    CSRF通常是跨域的,因为外域通常更容易被攻击者掌握,但是如果本域下有容易被利用的功能,比如可以发图和链接的论坛和评论区,攻击者可以在本域进行,这样更加危险。

防护策略

CSRF通常从第三方网站发起,被攻击的网站无法防止攻击发生,只能通过增加自己网站对CSRF的防护能力来提升安全性

  • CSRF通常发生在第三方域名
  • CSRF攻击者不能获取到cookie,只能使用
    针对这两点,我们可以采用以下策略:
  • 阻止不明外域的访问
    • 同源检测
    • Samesite cookie
  • 提交时要求附加本域才能获取的信息
    • CSRF token
    • 双重cookie验证
      以下是我们的详细说明

同源策略

既然csrf大多来自第三方网站,那我们就直接禁止不受信任的域名对我们发起请求。

那么我们如何判断请求是否来自外域呢?
在http协议中,每个异步请求都会携带两个header,用户标记来源域名

  • origin Header
  • Referer Header
    这两个Header在浏览器发起请求时,大多数情况会自动带上,并且不能由前端自定义内容,服务器可以通过解析这两个header中的域名,确定请求的来源域。

使用Origin Header确定来源域名

在部分与CSRF有关的请求中,请求的Header会带上origin字段,字段内包含请求的域名。
如果origin存在,那么直接使用origin字段确定来源域名即可。
但有时候origin有下面两种情况不存在

  • iE11 同源策略: Ie11不会在跨站cors请求上添加origin头,referer头仍是唯一标识,最根本原因是IE11对同源的定义和其他浏览器不同
  • 302重定向:在302重定向之后origin不包含在重定向的请求中,因为origin可能会被认为是其他源的敏感信息。对于302重定向的情况来说都是重定向到新的服务器URL上。因此浏览器不想将origin泄漏到新的服务器上

使用Referer Header来确定来源域名

根据HTTP协议,在http头中有一个字端叫referer,记录了该HTTP请求的来源地址,对于Ajax请求,图片和script等资源请求,Referer为发起请求的页面地址。对于页面跳转,referer为打开页面历史记录的前一个地址,因为我们使用Referer中链接的origin部分可以得知请求的来源域名。

这种方法并非万无一失,referer的值是由浏览器提供的,虽然Http协议上有明确的要求。但是每个浏览器对于Referer的具体实现可能有差别,并不能保证浏览器自身没有漏洞,使用验证referer值的方法,就是把安全性都依赖于第三方(即浏览器)来保证,从理论上讲,这样并不是很安全,在部分情况下,攻击者可以隐藏,甚至修改自己请求的referer。

无法确认来源域名情况

当origin和referer头文件不存在时该怎么办?如果origin和referer都不存在,建议直接进行阻止,特别是如果没有使用CSRF Token(参考下方)作为第二次检查。

如何阻止外域请求

通过Header的验证,我们可以知道发起请求的来源域名,这些来源域名可能是网站本域,或者有授权的第三方域名。又或者来自不可信的未知域名。我们直接阻止掉这些请求,就能预防CSRF攻击了吗?

但是,当一个请求是页面请求(比如网站的主页),而来源是搜索引擎的链接(例如百度的搜索结果),也会被当成一次csrf攻击。所以在判断的时候需要过滤页面请求,通常header符合以下情况

Accept: text/html
Method: GET

但相应的,页面请求就暴露在了CSRF的攻击范围之中,如果你的网站中,在页面GET请求中对用户做了什么操作的话,防范就失效了。

例如:GET https://example.com/addComment?comment=xxx&order=orderId
这种严格来说并不一定存在CSRF攻击的风险,但仍然有很多网站经常把主文档GET请求挂上参数来实现产品功能,但是这样做对于自身来讲是存在安全风险的。

另外,CSRF一般来自第三方域名,但是也有可能是同域的,比如攻击者有权限在本域发表评论(含链接、图片,统称UGC等),那么他可以直接在本域发起攻击,这种情况下同源策略无法发挥作用。

综上所述,同源验证是一个相对简单的防范方法,能够防范绝大多数的csrf攻击,但这并不是万无一失的,对于安全性较高,或者有较多用户输入的网站,我们就要对关键的接口做额外的防护。

CSRF Token

CSRF另一个特征是攻击者无法获取用户的信息(cookie,header,网站内容等)仅仅是冒用cookie中的信息。

而CSRF攻击之所以能够成功,是因为服务器误把攻击者的请求当成用户自己的请求,那么我们可以要求所有用户都携带一个CSRF攻击者无法获取到的token,服务器通过校验请求是否携带正确的token,来把正常的请求和攻击的请求分开,也可以防范csrf的攻击。

原理

csrf Token的防护策略分为3个步骤

将CSRF Token输出到页面中

首先用户打开页面的时候,服务器给这个用户生成一个token,该token通过加密算法对数据进行加密。一般token都包含随机字符串和时间戳的组合。显然在提交的时候不能再放cookie中了,不然又会被攻击者冒用,因为为了安全起见,token就存在服务器的session中,之后每次加载页面的时候,使用js添加dom中所有的a和form标签加入token。这样就可以解决大部分请求,但是在动态生成的html,这种方法就没有作用,需要手动添加。

页面提交请求携带这个token

对于GET请求,Token将附在请求地址之后,这样URL就变成了,http://url?csrftoken=tokenvalue
而对post 请求来说,要在form的最后加上:

<input type="hidden" name="csrftoken" value="tokenvalue" />

这样就把token以参数的形式加入请求了

服务器验证token是否正确

当用户从客户端得到了token,再次提交服务器的时候,服务器需要判断token的有效性,验证过程是先解密Token,对比加密字符串以及时间戳,如果加密字符串一致且时间未过期,那么这个token就是有效的。

这种方法要比之前检查referer或者origin更安全一些,Token可以在产生并放于session之中,然后在每次请求时把token从session中拿出,与请求的token进行对比,但这种方法的比较麻烦的在于如何把Token以参数的形式加入请求。

这个token的值必须是随机生成的,这样它就不会被攻击者猜到。在初始生成此token之后,该值将存储在会话中,并用于每个后续请求,直到会话过期。当最终用户发出请求时,服务端必须验证请求中的Token的存在性和有效性,与会话中找到的token相比较,如果在请求中找不到token,或者提供的值与会话中的值不匹配,则应中止请求,应重置token并将事件记录为正在进行的潜在csrf攻击。

分布式校验

在大型网站中,使用session存储csrf token会带来很大的压力,访问单台服务器session时同一个。但是现在的大型网站中,我们的服务器通常不止一台,可能是几十台甚至几百台,甚至多个机房都可能在不同的省份,由于session默认存储在单机服务器内存中,因此在分布式环境下同一个用户发送的多次Http请求可能会先后落在不同的服务器上,导致后面发起的http请求无法拿到之前存储在服务器session中的数据,从而使得session机制在分布式环境下失效,因此在分布式集群中csrf token需要存储在redis之类的公共存储空间。

由于使用session存储,读取和验证csrf token会引起比较大的复杂度和性能问题。目前很多网站采用encrypted Token Pattern方式。这种方法的token是一个计算出来的结果,而非随机生成的字符串。这样在校验时无需再去读取存储的token,只用再次计算一次即可。

这种Token的值通常是使用UserID、时间戳和随机数,通过加密的方式生成,这样既可以保证分布式服务的Token一致,又能保证Token不容易被破解。

在token解密成功之后,服务器可以访问解析值,Token中包含UserID和时间戳将会被拿来验证有效性,将UserID与当前登陆的UserId进行比较,并将时间戳与当前时间比较。

总结

Token是一个比较有效的CSRF防护方法,只要页面没有XSS漏洞泄露Token,那么接口的CSRF就无法成功。

但是此方法的实现比较复杂,需要给每一个页面写入token,每一个form和ajax请求都携带这个toen,后端对每个接口都进行校验,并保证token和请求token一致,

这就使得这个防护策略不能在通用的拦截上统一处理,而需要每个页面和接口都添加对应的输出和校验,这种方法工作量巨大,且可能会有遗漏。

验证码和密码其实也可以起到CSRF Token的作用,而且更安全。
为什么很多银行等网站要求已经登陆的用户在转账时再次输入密码,是不是有一定道理了?

双重cookie验证

在绘会话存储csrf token比较繁琐,而且不能在通用的拦截上统一处理所有的接口。
那么另一种防御措施是使用双重提交cookie,利用csrf攻击不能获取用户cookie的特点,我们可以要求Ajax和表单请求携带一个cookie的值

双重cookie采用以下流程

  • 在用户访问网站页面时,向请求域名注入一个cookie,内容为随机字符串(例如:csrfcookie=xxx
  • 在前端向后端发起请求时,取出cookie,并添加到URL的参数中(例如:https://xxx.com/comment?csrfcookie=xxx
  • 后端接口验证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攻击

总结

用双重cookie防御CSRF的优点

  • 无需使用session,适用面广,易于实施
  • Token存储在客户端中,不会给服务器带来压力
  • 相对于Token,实施成本更低,可以在前后端统一拦截校验,而不需要一个个接口和页面添加。

缺点

  • cookie增加了额外字段
  • 如果有其他漏洞(例如XSS),攻击者可以注入cookie,那么该防御方式失效
  • 难以做到子域名的隔离
  • 为了确保cookie传输安全,采用这种防御方式最好采用https,不然也会有风险

防止网站被利用

CSRF攻击可能来自

  • 攻击者自己的网站
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值