1. 程式人生 > >跨站點請求偽造解決方案

跨站點請求偽造解決方案

AppScan

跨站點請求偽造

Token

近期通過APPScan掃描程式,發現了不少安全問題,通過大量查閱和嘗試最終還是解決掉了,於是整理了一下方便查閱。

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:

public void doFilter (ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { String referer=request.getHeader( "Referer" ); if ((referer!= null ) &&(referer.trim().startsWith( "bank.example" ))){ chain.doFilter(request, response); } else { request.getRequestDispatcher( "error.

jsp" ).forward(request,response); } } 2. 在請求引數中新增token驗證:

要抵禦跨站點請求偽造就要設定一個黑客偽造不了的東西。我們可以在請求引數中加一個隨機token,在伺服器驗證這個token,通過即銷燬重設。下面說一下我的實現:

首先定義token為 key-value 結構,因為很多情況會從不同的地方訪問同一個請求,如果是單一的資料結構,第一個請求生成token後還沒來得及傳送請求,第二個又請求生成token就會把第一個沖掉,從而導致連續的驗證失敗。所以,我們要通過請求源將token隔離起來。這裡我將請求地址摘要後作為token的key,用GUID作為token的value,程式碼如下:

/** * 根據請求地址獲取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 tokenMap = null ; Object obj = request.getSession().getAttribute( "tokenMap" ); if (obj == null ){ tokenMap = new HashMap(); request.getSession().setAttribute( "tokenMap" , tokenMap); } else { tokenMap = (Map)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 tokenMap = getTokenMap(request); if (value.equals(tokenMap.get(key))){ result = true ; tokenMap.remove(key); //成功一次就失效 } } return result; }

完成上邊的工具方法後,需要在form中新增token,如下:

< form name = "frm" action = "/test/tokentest.htm" method = "POST" > < input type = "hidden" name = "token_key" value = "