跨站点请求伪造解决方案

近期通过APPScan扫描程序,发现了不少安全问题,通过大量查阅和尝试最终还是解决掉了,于是整理了一下方便查阅。

前一篇博客介绍了启用了不安全的HTTP方法的解决方案,有兴趣请移步http://www.cnblogs.com/xlyslr/p/5707995.html

1.跨站点请求伪造

首先,什么是跨站点请求伪造?

跨站点请求伪造-CSRF(Cross Site Request Forgery):是一种网络攻击方式。

说的白话一点就是,别的站点伪造你的请求,最可怕的是你还没有察觉并且接收了。听起来确实比较危险,下面有个经典的实例,了解一下跨站点请求伪造到底是怎么是实现的,知己知彼。

受害者:Bob
黑客:Mal
银行:bank
bob在银行有一笔存款,可以通过请求http://bank.example/withdraw?account=bob&amount=1000000&for=bob2把钱转到bob2下。通常情况下,该请求到达网站后,服务器会验证请求是否来自一个合法的session,并且该session的用户Bob已登录。Mal在该银行也有账户,于是他伪造了一个地址http://bank.example/withdraw?account=bob&amount=1000000&for=mal,但是如果直接访问,服务器肯定会识别出当前登录用户是mal而不是Bob,不能接受请求。于是通过CSRF攻击方式,将此链接伪造在广告下,诱使Bob自己点这个链接,那么请求就会携带Bob浏览起的cookie一起发送到银行,而Bob同时又登录了银行或者刚刚登录不久session还没有过期,那服务器发现cookie中有Bob的登录信息,就接收了响应,攻击就成功了

2.现在主要的几种防御CSRF的策略:

1. 验证Referer:

referer携带请求来源,从示例可以看出,受害者发送非法请求肯定不是在银行的界面,所以在服务器通过验证Referer是不是bank.example开始就可以了,这个方法简单粗暴。

最简单的实现就是加个Filter:

  1. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
  2. throws IOException, ServletException 

  3. String referer=request.getHeader("Referer");  
  4. if((referer!=null) &&(referer.trim().startsWith("bank.example"))){  
  5. chain.doFilter(request, response);  
  6. }else{  
  7. request.getRequestDispatcher("error.jsp").forward(request,response);  


2. 在请求参数中添加token验证:

要抵御跨站点请求伪造就要设置一个黑客伪造不了的东西。我们可以在请求参数中加一个随机token,在服务器验证这个token,通过即销毁重设。下面说一下我的实现:

首先定义token为key-value结构,因为很多情况会从不同的地方访问同一个请求,如果是单一的数据结构,第一个请求生成token后还没来得及发送请求,第二个又请求生成token就会把第一个冲掉,从而导致连续的验证失败。所以,我们要通过请求源将token隔离起来。这里我将请求地址摘要后作为token的key,用GUID作为token的value,代码如下:

  1. /** 
  2. * 根据请求地址获取token-key 
  3. */ 
  4. public static String getTokenKey(HttpServletRequest request)
  5. String key = null
  6. try
  7. MessageDigest mDigest = MessageDigest.getInstance("MD5");//摘要算法可以自己选择 
  8. byte[] result = mDigest.digest(request.getRequestURL().toString().getBytes()); 
  9. key = StringUtil.bytes2hex(result); 
  10. } catch (NoSuchAlgorithmException e) { 
  11. LOGGER.error("get token key failed",e); 
  12. }  
  13. return key 

  14.  
  15. /** 
  16. * 获取token-value并存储在session中 
  17. */ 
  18. public static String getTokenValue(HttpServletRequest request)
  19. String key = getTokenKey(request); 
  20. Map<String,String> tokenMap = null
  21. Object obj = request.getSession().getAttribute("tokenMap"); 
  22. if(obj == null){ 
  23. tokenMap = new HashMap<String,String>(); 
  24. request.getSession().setAttribute("tokenMap", tokenMap); 
  25. } else
  26. tokenMap = (Map<String,String>)obj; 

  27. if(tokenMap.containsKey(key)){ 
  28. return tokenMap.get(key); 

  29. String value = GUID.generate();//GUID实现可自行百度,其实弄个伪随机数也是可以的... 
  30. tokenMap.put(key,value); 
  31. return value; 

  32.  
  33. /** 
  34. * 验证token 
  35. */ 
  36. public static boolean verify(String key ,String value ,HttpServletRequest request)
  37. boolean result = false
  38. if (StringUtil.isEmpty(key) || StringUtil.isEmpty(value)) {//key或value只要有一个不存在就验证不通过 
  39. return result; 

  40.  
  41. if (request.getSession() != null) { 
  42. Map<String,String> tokenMap = getTokenMap(request); 
  43. if(value.equals(tokenMap.get(key))){ 
  44. result = true
  45. tokenMap.remove(key);//成功一次就失效 


  46. return result; 

完成上边的工具方法后,需要在form中添加token,如下:

  1. <form name="frm" action="/test/tokentest.htm" method="POST"> 
  2. <input type="hidden" name="token_key" value="<%=Token.getTokenKey(request) %>"/> 
  3. <input type="hidden" name="token_value" value="<%=Token.getTokenValue(request) %>"/> 
  4. ... 
  5. </form> 

验证可以放在Filter里也可以放在Service里,只要保证请求/test/tokentest.htm会先验证就行了。直接调用工具方法Token.verify()以下就不赘述了。

3. 在HTTP头中自定义属性并验证:

这个方法和上面那个类似,也是设置token,只是把token设置为HTTP头中的自定义属性。

通过XMLHttpRequest可以一次性给所有该类请求的HTTP头加上token 属性,但是XMLHttpRequest请求通常用于Ajax方法对局部页面的异步刷新,比较有局限性;而且通过XMLHttpRequest请求的地址不会被记录到浏览器的地址栏,一方面不会通过Referer泄露token,另一方面会导致前进,后退,刷新,收藏等操作失效,所以还是慎用。

虽然上面介绍了几种方法,但现在还没有一种完美的解决方案,但是通过Referer和Token方案结合起来使用,也能很得有效CSRF攻击。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值