前言:
在开发中相信相信很多人都会注意到CSRF这个东西,那这个是什么呢,隐隐约约只知道这是个安全策略。但是具体做什么,有什么用,防哪些安全隐患的却知道的不甚清楚。笔者带着疑问查了一些资料,在这里做个总结。
一、CSRF
1.CSRF是什么?
跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。以上的解释就是官方给的CSRF的解释,解释比较简短,若是需要深刻理解CSRF的攻击方式还需要通过例子来说明CSRF的攻击过程。下面就来详细说下CSRF的攻击过程。
2.CSRF如何实现攻击?
上面这张图就是一个典型的CSRF攻击的流程:
第一步:Tom登录银行网站没有退出,此时Tom使用的浏览器中包含了Tom在银行的身份验证信息。
第二步:黑客或者称为攻击人员在一个帖子中加入了超链接,这个超链接就是Tom已经登录的银行的转账地址。
第三步:Tom在没有正常退出银行网站时,就去点击了第二步攻击人员设置的帖子。
第四步:银行收到攻击人员设置的转账请求,然后发现当前请求是Tom发起的(因为浏览器Cookie中仍存在Tom的登录信息),银行发起转账此时Tom的账户资金就被窃取了。
我们可以发现以上攻击可以实施的前提是第一步和第二步都满足了,只有满足了这两步Tom去浏览攻击者的帖子才会有账户被盗的风险,有人觉得这只是一个低概率事件,并不一定会触发这种场景,但是在安全的范围内这是一个漏洞,是企业中严令禁止的。我们不能将风险放任到客户自己的操作上,这样一旦客户操作失误,就会出现重大的生产事故,所以这种CSRF的攻击方式是需要每个项目去考虑的,那这种风险应该怎么去解决呢?下面就来说下如何预防CSRF这种攻击方式。
二、预防CSRF攻击的方式
要说预防CSRF的攻击,必须要说Referer,那Referer是什么呢?
1.Referer是什么?
Referer是Http请求头header的一部分,当浏览器向web服务器发送请求的时候,一般会带上Referer,告诉服务器该网页是从哪个页面链接过来的,服务器因此可以获得一些信息用于处理。Referer的正确英语拼法是referrer。由于早期HTTP规范的拼写错误,为了保持向后兼容就将错就错了。其它网络技术的规范企图修正此问题,使用正确拼法,所以拼法出现了不统一的情况。
上面是官方给出的Referer的定义,现在已经清楚了,当使用浏览器访问web服务器时,请求头中就会有Referer这个字段,这个字段用来表示请求的来源,下面是一个例子:
可以看到笔者打开的W3Cschool网站中点开的一个链接,当前的请求头中就出现了referer这个字段。这里必须要说明的是该字段可以实现不发送,因为笔者是后端开发,对于实现的机制也不是太了解。但是是可以实现的,所以很多请求点开是不会有Referer这个字段的。那我们要怎么使用Referer来预防CSRF攻击呢?
2.使用Referer来预防CSRF攻击
上面给的Tom账户被盗的例子,这里接着用这个例子来说明,当Tom登录了银行系统去发起转账时,那么此时转账请求中的Referer的值应该是银行网站的域名,但是当Tom点击了攻击者的帖子而发起转账时Referer的值应该是帖子所在的域名。这两者是不同的。所以如果银行系统在每次发起转账时对请求头中的Referer进行验证,只对于Referer是本站内的域名时才放行操作,就可以杜绝这种攻击。这种预防CSRF攻击的解决方案是最简单的一种,也是应用最多的一种,笔者目前正在做的项目就是使用的这种方法。
那Referer是绝对安全的吗?
当然也不是,没有一种技术是无懈可击的,这种使用Referer来判断请求来源的方式也不是百分百安全的。因为有工具可以篡改Referer的值,让你以为是真的客户发送的请求,此外Referer是http的规范,有的浏览器并没有实现,当然了主流的浏览器基本全都实现了,但是不能保证所有人使用的浏览器都是主流浏览器,所以这种解决方式并不是百分百安全的。
3.另一种预防CSRF攻击的策略
CSRF的攻击策略很明显就是借助用户浏览器的cookie信息来完成伪登录的,那么只要我们使用的验证信息不在cookie中,CSRF就不能完成攻击,现在常用的就是前端每次访问接口都是需要验证token,前后端的交互依赖token来进行,至于前端token存储在哪,反正不能存在cookie中,可以存sessionstorage等地方,这样一旦token验证失败,就认为是不正常的操作。
三、生产预防CSRF实践
笔者目前所在的项目是一个电商项目,对于预防CSRF的攻击使用了Referer验证与Token验证的方式来预防。双重保证下基本不会有CSRF攻击的出现,具体的措施是使用了两个拦截器,一个token登录拦截器,该拦截器拦截用户传递的token然后去统一用户进行验证,一个是CSRF拦截器验证请求头中的Referer是否是已信任的域名。
下面展示下CSRF拦截器关键代码。
1.CSRF拦截器
这种拦截很简单,就是获取请求头中的Referer然后和已配置的白名单进行比对,发现是白名单一员,则允许通过,若不是则拒绝。
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception { String referer = request.getHeader("Referer"); String flag = getFlagValue();//拦截器开关 String whiteUrls = getWhiteUrlValues();//获取Referer白名单,只有白名单成员的请求才可以进入到系统 //判断是否启用refer过滤 1:开启; 0:关闭 if ("1".equals(flag)) { if (StringUtil.isNotNull(referer)) { if (StringUtil.checkUrlCSRF(referer, whiteUrls)) { return true; } else { returnCSRF(response); return false; } } else { returnCSRF(response); return false; } } return true; } public static boolean checkUrlCSRF(String refer, String whileUrls) { if(isNotNull(refer)){ List<String> whiteUrlList = new ArrayList<String>(); whiteUrlList = Arrays.asList(whileUrls.split(";")); if(whiteUrlList != null && whiteUrlList.size() > 0){ for(int i = 0; i < whiteUrlList.size(); i++){ if(refer.indexOf(whiteUrlList.get(i)) == 0){ return true; } } return false; } } return true; } private void returnCSRF(HttpServletResponse response) throws Exception { ResultDTO resultDTO = new ResultDTO(ExceptionEnum.AUTHORITY_CSRF_30.getResultCode(), null, ExceptionEnum.AUTHORITY_CSRF_30.getResultMsg()); response.setStatus(403); response.setHeader("Access-Control-Allow-Origin", "-"); response.setHeader("Access-Control-Allow-Credentials", "false"); JSONObject jsonObject = new JSONObject(); jsonObject.put("resultDTO", resultDTO); JsonSerializerUtil.returnJson(response, jsonObject.toString()); resultDTO = null; }
四、总结
这篇文章总结了CSRF攻击的原理,以及怎么去预防CSRF的攻击。然后说明了现在互联网公司是如何来解决CSRF攻击的场景,希望看到此篇文章的人从此对CSRF都不在迷惑,也可以从容应对这种攻击手段。CSRF攻击是一种偏向于安全的东西,但是作为后端开发确实是必须掌握的,假如需要你设计一个系统架构,架构是设计出来了,但是安全漏洞有很多等于没用,如果攻击者可以轻松的攻击你的系统,那这个系统还有什么存在的意义呢。