CSRF 攻擊的應對之道

安全系列文章首發javascript

Java 代碼示例

下文將以 Java 爲例,對上述三種方法分別用代碼進行示例。不管使用何種方法,在服務器端的攔截器必不可少,它將負責檢查到來的請求是否符合要求,而後視結果而決定是否繼續請求或者丟棄。在 Java 中,攔截器是由 Filter 來實現的。咱們能夠編寫一個 Filter,並在 web.xml 中對其進行配置,使其對於訪問全部須要 CSRF 保護的資源的請求進行攔截。html

在 filter 中對請求的 Referer 驗證代碼以下java

清單 1. 在 Filter 中驗證 Referer
// 從 HTTP 頭中取得 Referer 值
String referer=request.getHeader("Referer"); 
// 判斷 Referer 是否以 bank.example 開頭
if((referer!=null) &&(referer.trim().startsWith(「bank.example」))){ 
   chain.doFilter(request, response); 
}else{ 
   request.getRequestDispatcher(「error.jsp」).forward(request,response); 
}
複製代碼

以上代碼先取得 Referer 值,而後進行判斷,當其非空並以 bank.example 開頭時,則繼續請求,不然的話多是 CSRF 攻擊,轉到 error.jsp 頁面。web

若是要進一步驗證請求中的 token 值,代碼以下算法

清單 2. 在 filter 中驗證請求中的 token
HttpServletRequest req = (HttpServletRequest)request; 
HttpSession s = req.getSession(); 
 
// 從 session 中獲得 csrftoken 屬性
String sToken = (String)s.getAttribute(「csrftoken」); 
if(sToken == null){ 
 
   // 產生新的 token 放入 session 中
   sToken = generateToken(); 
   s.setAttribute(「csrftoken」,sToken); 
   chain.doFilter(request, response); 
} else{ 
 
   // 從 HTTP 頭中取得 csrftoken 
   String xhrToken = req.getHeader(「csrftoken」); 
 
   // 從請求參數中取得 csrftoken 
   String pToken = req.getParameter(「csrftoken」); 
   if(sToken != null && xhrToken != null && sToken.equals(xhrToken)){ 
       chain.doFilter(request, response); 
   }else if(sToken != null && pToken != null && sToken.equals(pToken)){ 
       chain.doFilter(request, response); 
   }else{ 
       request.getRequestDispatcher(「error.jsp」).forward(request,response); 
   } 
}
複製代碼

首先判斷 session 中有沒有 csrftoken,若是沒有,則認爲是第一次訪問,session 是新創建的,這時生成一個新的 token,放於 session 之中,並繼續執行請求。若是 session 中已經有 csrftoken,則說明用戶已經與服務器之間創建了一個活躍的 session,這時要看這個請求中有沒有同時附帶這個 token,因爲請求可能來自於常規的訪問或是 XMLHttpRequest 異步訪問,咱們分別嘗試從請求中獲取 csrftoken 參數以及從 HTTP 頭中獲取 csrftoken 自定義屬性並與 session 中的值進行比較,只要有一個地方帶有有效 token,就斷定請求合法,能夠繼續執行,不然就轉到錯誤頁面。生成 token 有不少種方法,任何的隨機算法均可以使用,Java 的 UUID 類也是一個不錯的選擇。安全

除了在服務器端利用 filter 來驗證 token 的值之外,咱們還須要在客戶端給每一個請求附加上這個 token,這是利用 js 來給 html 中的連接和表單請求地址附加 csrftoken 代碼,其中已定義 token 爲全局變量,其值能夠從 session 中獲得。服務器

清單 3. 在客戶端對於請求附加 token
function appendToken(){ 
   updateForms(); 
   updateTags(); 
} 
 
function updateForms() { 
   // 獲得頁面中全部的 form 元素
   var forms = document.getElementsByTagName('form'); 
   for(i=0; i<forms.length; i++) { 
       var url = forms[i].action; 
 
       // 若是這個 form 的 action 值爲空,則不附加 csrftoken 
       if(url == null || url == "" ) continue; 
 
       // 動態生成 input 元素,加入到 form 以後
       var e = document.createElement("input"); 
       e.name = "csrftoken"; 
       e.value = token; 
       e.type="hidden"; 
       forms[i].appendChild(e); 
   } 
} 
 
function updateTags() { 
   var all = document.getElementsByTagName('a'); 
   var len = all.length; 
 
   // 遍歷全部 a 元素
   for(var i=0; i<len; i++) { 
       var e = all[i]; 
       updateTag(e, 'href', token); 
   } 
} 
 
function updateTag(element, attr, token) { 
   var location = element.getAttribute(attr); 
   if(location != null && location != '' '' ) { 
       var fragmentIndex = location.indexOf('#'); 
       var fragment = null; 
       if(fragmentIndex != -1){ 
 
           //url 中含有隻至關頁的錨標記
           fragment = location.substring(fragmentIndex); 
           location = location.substring(0,fragmentIndex); 
       } 
        
       var index = location.indexOf('?'); 
 
       if(index != -1) { 
           //url 中已含有其餘參數
           location = location + '&csrftoken=' + token; 
       } else { 
           //url 中沒有其餘參數
           location = location + '?csrftoken=' + token; 
       } 
       if(fragment != null){ 
           location += fragment; 
       } 
        
       element.setAttribute(attr, location); 
   } 
}
複製代碼

在客戶端 html 中,主要是有兩個地方須要加上 token,一個是表單 form,另外一個就是連接 a。這段代碼首先遍歷全部的 form,在 form 最後添加一隱藏字段,把 csrftoken 放入其中。而後,代碼遍歷全部的連接標記 a,在其 href 屬性中加入 csrftoken 參數。注意對於 a.href 來講,可能該屬性已經有參數,或者有錨標記。所以須要分狀況討論,以不一樣的格式把 csrftoken 加入其中。session

若是你的網站使用 XMLHttpRequest,那麼還須要在 HTTP 頭中自定義 csrftoken 屬性,利用 dojo.xhr 給 XMLHttpRequest 加上自定義屬性代碼以下:app

清單 4. 在 HTTP 頭中自定義屬性
var plainXhr = dojo.xhr; 
 
// 重寫 dojo.xhr 方法
dojo.xhr = function(method,args,hasBody) { 
   // 確保 header 對象存在
   args.headers = args.header || {}; 
        
   tokenValue = '<%=request.getSession(false).getAttribute("csrftoken")%>'; 
   var token = dojo.getObject("tokenValue"); 
    
   // 把 csrftoken 屬性放到頭中
   args.headers["csrftoken"] = (token) ? token : " "; 
   return plainXhr(method,args,hasBody); 
};
複製代碼

這裏改寫了 dojo.xhr 的方法,首先確保 dojo.xhr 中存在 HTTP 頭,而後在 args.headers 中添加 csrftoken 字段,並把 token 值從 session 裏拿出放入字段中。異步

相關文章
相關標籤/搜索