跨站點請求偽造解決方案


跨站點請求偽造解決方案

近期通過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攻擊。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM