Rails CSRF安全性功能以及取消form驗證
預設產生出來的 Controller 都繼承自 applicationController 。因此定義在這裡的方法可以被所有 Controller 取用,你可以在這邊定義一些共用的方法。預設的 application_controller.rb 長的如下:
class ApplicationController < ActionController::Base
PRotect_from_forgery
end
其中的 protect_from_forgery 方法啟動了 CSRF 安全性功能,所有非 GET 的 HTTP request都必須帶有一個 Token 參數才能存取, Rails 會自動在所有表單中幫你插入 Token 參數,預設的Layout 中也有一行 <%= csrf_meta_tag %> 標籤可以讓 javaScript 讀取到這個 Token 。會需要關閉這個功能的時機是,你需要開放 API 給非瀏覽器客戶端,這時候你會需要取消它:
class ApisController < ApplicationController
skip_before_filter :verify_authenticity_token
end
首先,什么是跨站點請求偽造?
跨站點請求偽造-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:
[java] view plain copy/** * 根據請求地址獲取token-key */ public static String getTokenKey(HttpServletRequest request){ String key = null; try { MessageDigest mDigest = MessageDigest.getInstance("md5");//摘要算法可以自己選擇 byte[] result = mDigest.digest(request.getRequestURL().toString().getBytes()); key = StringUtil.bytes2hex(result); } catch (NoSuchAlgorithmException e) { LOGGER.error("get token key failed",e); } return key } /** * 獲取token-value并存儲在session中 */ public static String getTokenValue(HttpServletRequest request){ String key = getTokenKey(request); Map<String,String> tokenMap = null; Object obj = request.getSession().getAttribute("tokenMap"); if(obj == null){ tokenMap = new HashMap<String,String>(); request.getSession().setAttribute("tokenMap", tokenMap); } else { tokenMap = (Map<String,String>)obj; } if(tokenMap.containsKey(key)){ return tokenMap.get(key); } String value = GUID.generate();//GUID實現可自行百度,其實弄個偽隨機數也是可以的... tokenMap.put(key,value); return value; } /** * 驗證token */ public static boolean verify(String key ,String value ,HttpServletRequest request){ boolean result = false; if (StringUtil.isEmpty(key) || StringUtil.isEmpty(value)) {//key或value只要有一個不存在就驗證不通過 return result; } if (request.getSession() != null) { Map<String,String> tokenMap = getTokenMap(request); if(value.equals(tokenMap.get(key))){ result = true; tokenMap.remove(key);//成功一次就失效 } } return result; } 完成上邊的工具方法后,需要在form中添加token,如下:[html] view%20plain copy
<form name="frm" action="/test/tokentest.htm" method="POST"> <input type="hidden" name="token_key" value="<%=Token.getTokenKey(request) %>"/> <input type="hidden" name="token_value" value="<%=Token.getTokenValue(request) %>"/> ... </form>
驗證可以放在Filter里也可以放在Service里,只要保證請求
/test/tokentest.htm會先驗證就行了。直接調用工具方法Token.verify()以下就不贅述了。3. 在HTTP頭中自定義屬性并驗證:
這個方法和上面那個類似,也是設置token,只是把token設置為HTTP頭中的自定義屬性。
通過xmlHttpRequest可以一次性給所有該類請求的HTTP頭加上token 屬性,但是xmlhttpRequest請求通常用于Ajax方法對局部頁面的異步刷新,比較有局限性;而且通過XMLHttpRequest請求的地址不會被記錄到瀏覽器的地址欄,一方面不會通過Referer泄露token,另一方面會導致前進,后退,刷新,收藏等操作失效,所以還是慎用。
雖然上面介紹了幾種方法,但現在還沒有一種完美的解決方案,但是通過Referer和Token方案結合起來使用,也能很得有效CSRF攻擊。
新聞熱點
疑難解答