Java 安全之:csrf攻击总结

  最近在维护一些老项目,调试时发现请求屡屡被拒绝,仔细看了一下项目的源码,发现有csrf token校验,借这个机会把csrf攻击学习了一下,总结成文。本文主要总结什么是csrf攻击以及有哪些方法来防范,接下来会再写一篇文章,从源码中来学习一下实战中是如何防护csrf攻击的。php

  主要内容以下:前端

  什么是CSRF攻击程序员

  几种常见的攻击类型算法

  CSRF的特色后端

  防御策略api

  总结跨域

 

1. 什么是CSRF攻击

  CSRF(Cross-site request forgery)跨站请求伪造:攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证(好比cookie),绕事后台的用户验证,达到冒充用户对被攻击的网站执行某项操做的目的。浏览器

  一个典型的CSRF攻击有着以下的流程:安全

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

2. 几种常见的攻击类型

2.1 GET类型的CSRF

  GET类型的CSRF利用很是简单,只须要一个HTTP,通常会这样利用:服务器

<img src="http://bank.example/withdraw?account&=xx&amount=100&to=hacker" >

  在受害者访问含有这个img的页面以后,浏览器会自动向http://bank.example/withdraw?account=xx&amount=100&to=hacker发送一次HTTP请求。bank.example就会收到包含受害者登陆信息的一次跨域请求。

2.2 Post类型的CSRF

  这种类型的CSRF利用起来一般使用的是一个自动提交的表单,如:

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

  访问该页面后,表单会自动提交,至关于模拟用户完成了一次POST操做。

  POST类型的攻击一般比GET要求更加严格一点,但并不复杂。任何我的网站、博客,被黑客上传页面的网站都有多是发起攻击的来源,后端接口不能将安全寄托在仅容许POST上面。

2.3 连接类型的CSRF

  连接类型的CSRF并不常见,比起其余两种用户打开页面就会中招的状况,这种须要用户点击连接才会触发。这种类型一般是在论坛中发布图片中嵌入恶意连接,或者以广告的形式诱导用户中招,攻击者一般会以比较夸张的词语诱骗用户点击,例如:

<a href="https://bank.example.com/csrf/withdraw.php?amount=100&to=hacker" target="_blank">
    文章和马伊琍离婚!!!
</a>

  因为以前用户已经登陆,在登陆状态还未过时时,只要用户主动访问上面的这个PHP页面,则攻击成功。

3. CSRF的特色

  • 攻击通常发起在第三方网站,而不是被攻击的网站。被攻击的网站没法防止攻击发生。
  • 攻击利用受害者在被攻击网站的登陆凭证,冒充受害者提交操做;而不是直接窃取数据。
  • 整个过程攻击者并不能获取到受害者的登陆凭证,仅仅是“冒用”。
  • 跨站请求能够用各类方式:图片URL、超连接、CORS、Form提交等等。部分请求方式能够直接嵌入在第三方论坛、文章中,难以进行追踪。

  CSRF一般是跨域的,由于外域一般更容易被攻击者掌控。可是若是本域下有容易被利用的功能,好比能够发图和连接的论坛和评论区,攻击能够直接在本域下进行,并且这种攻击更加危险。

4. 防御策略

  CSRF一般从第三方网站发起,被攻击的网站没法防止攻击发生,只能经过加强本身网站针对CSRF的防御能力来提高安全性。上文中讲了CSRF的两个特色:

  • CSRF(一般)发生在第三方域名;
  • CSRF攻击者不能获取到Cookie等信息,只是使用;

  针对这两点,有以下经常使用的防御策略,下面会有详细说明:

  • 同源检测
  • CSRF Token
  • 双重Cookie验证

4.1 同源检测

  这属于在提交时要求附加本域才能获取的信息的方式,这是源于CSRF大多来自第三方网站,经过直接禁止外域(或者不受信任的域名)对咱们发起请求的方式来防御攻击,那又是如何来实现这种方式呢?

  在HTTP协议中,有一个Header叫Referer,用于标记来源域名。在浏览器发起请求时,大多数状况会自动带上这个Header,而且不能由前端自定义其内容,因此服务器能够经过解析这个Header中的域名,肯定请求的来源域。对于Ajax请求,图片和script等资源请求,Referer为发起请求的页面地址。对于页面跳转,Referer为打开页面历史记录的前一个页面地址。所以经过Referer中连接的Origin部分就能够得知请求的来源域名。

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

  前面说过,CSRF大多数状况下来自第三方域名,但并不能排除本域发起。若是攻击者有权限在本域发布评论(含连接、图片等,统称UGC),那么它能够直接在本域发起攻击,这种状况下同源策略没法达到防御的做用。

总结

  同源验证是一个相对简单的防范方法,可以防范绝大多数的CSRF攻击。但这并非万无一失的,对于安全性要求较高,或者有较多用户输入内容的网站,咱们就要对关键的接口作额外的防御措施。

4.2 CSRF Token校验

   CSRF的另外一个特征是,攻击者没法直接窃取到用户的信息(Cookie,Header,网站内容等),仅仅是冒用Cookie中的信息。而CSRF攻击之因此可以成功,是由于服务器误把攻击者发送的请求当成了用户本身的请求。那么咱们能够要求全部的用户请求都携带一个CSRF攻击者没法获取到的Token。服务器经过校验请求是否携带正确的Token来把正常的请求和攻击的请求区分开,这也能够防范CSRF的攻击。

  基于CSRF Token的防御策略大体分为三个步骤:

1.将CSRF Token输出到页面中

  首先,用户打开页面的时候,服务器须要给这个用户生成一个Token,该Token经过加密算法对数据进行加密,通常Token都包括随机字符串和时间戳的组合,显然在提交时Token不能再放在Cookie中了,不然又会被攻击者冒用。所以,为了安全起见Token最好仍是存在服务器的Session中,以后在每次页面加载时,使用JS遍历整个DOM树,对于DOM中全部的a和form标签后加入Token。这样能够解决大部分的请求,可是对于在页面加载以后动态生成的HTML代码,这种方法就没有做用,还须要程序员在编码时手动添加Token。

2.页面提交的请求携带这个Token

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

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

3.服务器验证Token是否正确

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

  这种方法要比以前检查Referer或者Origin要安全一些,Token能够在产生并放于Session之中,而后在每次请求时把Token从Session中拿出,与请求中的Token进行比对,但这种方法的比较麻烦的在于如何把Token以参数的形式加入请求。 下面将以Java为例,介绍一些CSRF Token的服务端校验逻辑,代码以下:

HttpServletRequest req = (HttpServletRequest)request;
HttpSession s = req.getSession();
// 从sesion中获得csrftoken属性
String sToken = (String)s.getAttribute("csrftoken");
if(sToken == null){
    // 产生新的token放入session中
    sToken = generateToken();
    s.setAttribute("csrftoken",sToken);
    chain.doFilter(request,response);
}else{
    // 从HTTP头中取得csrftoken
    String xhrToken = req.getHeader("csrftoken");
    // 从请求参数中取得csrftoken
    String pToken = req.getParameter("csrftoken");
    if(sToken != null && xhrToken != null && sToken.equals(xhrToken)){
        chain.doFilter(request,response);
    }else if(sToken != null & pToken != null && sToken.equals(pToken)){
        chain.doFilter(request,response);
    }else{
        request.getRequestDispatcher("error.jsp").forward(request,response);
    }
}

总结

  Token是一个比较有效的CSRF防御方法,只要页面没有XSS漏洞泄露Token,那么接口的CSRF攻击就没法成功。可是此方法的实现比较复杂,须要给每个页面都写入Token(前端没法使用纯静态页面),每个Form及Ajax请求都携带这个Token,后端对每个接口都进行校验,并保证页面Token及请求Token一致。这就使得这个防御策略不能在通用的拦截上统一拦截处理,而须要每个页面和接口都添加对应的输出和校验。这种方法工做量巨大,且有可能遗漏。

4.3 双重Cookie验证

  在会话中存储CSRF Token比较繁琐,并且不能在通用的拦截上统一处理全部的接口。

  那么另外一种防护措施是使用双重提交Cookie。利用CSRF攻击不能获取到用户Cookie的特色,咱们能够要求Ajax和表单请求携带一个Cookie中的值。  

  双重Cookie采用如下流程:

  • 在用户访问网站页面时,向请求域名注入一个Cookie,内容为随机字符串(例如csrfcookie=v8g9e4ksfhw)。
  • 在前端向后端发起请求时,取出Cookie,并添加到URL的参数中(接上例POST https://www.a.com/comment?csrfcookie=v8g9e4ksfhw)。
  • 后端接口验证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的方式,若是还没切HTTPS的使用这种方式也会有风险。

5. 总结

  本文简单总结了一下CSRF的防御策略:

  • CSRF自动防护策略:同源检测(Origin 和 Referer 验证);
  • CSRF主动防护措施:Token验证 或者 双重Cookie验证;

  除此以外,保证页面的幂等性,后端接口不要在GET页面中作用户操做。为了更好的防护CSRF,最佳实践应该是结合上面总结的防护措施方式中的优缺点来综合考虑,结合当前Web应用程序自身的状况作合适的选择,才能更好的预防CSRF的发生。

 

 

参考文献:

前端安全系列之:如何防止 CSRF 攻击?

相关文章
相关标签/搜索