跨站點請求偽造攻擊(CSRF)在Web應用程序中非常常見,如果允許,可能會造成重大危害。 如果您從未聽說過CSRF,建議您查看有關它的OWASP頁面 。
幸運的是,阻止CSRF攻擊非常簡單,我將向您展示它們的工作方式,以及如何在基于Java的Web應用程序中以盡可能不干擾的方式防御它們。
想象一下,您即將在銀行的安全網頁上進行匯款,當您單擊轉帳選項時,將加載一個表格頁面,您可以選擇借方和貸方帳戶,并輸入要轉移的金額。 當您對選擇感到滿意時,請按“提交”,然后將表單信息發送到銀行的Web服務器,該服務器依次執行交易。
現在,將以下內容添加到圖片中,一個惡意網站(您認為當然沒有害處)在瀏覽器的另一個窗口/選項卡上打開,而您無辜地在銀行站點中移動了數百萬美元。 這個邪惡的網站了解銀行的網絡表單結構,當您瀏覽該網站時,它會嘗試發布從您的帳戶中提取資金并將其存入邪惡的霸主賬戶的交易,之所以能夠這樣做,是因為您與銀行之間存在公開且有效的會話銀行網站使用同一瀏覽器! 這是CSRF攻擊的基礎。
一種簡單有效的預防方法是在加載初始傳輸表單時生成一個隨機(即,不可預測的)字符串并將其發送給瀏覽器。 然后,瀏覽器將這些數據與傳輸選項一起發送,并且服務器會在批準交易進行處理之前對其進行驗證。 這樣,惡意網站即使可以訪問瀏覽器中的有效會話也無法發布交易。
為了在Java中實現此機制,我選擇使用兩個過濾器,一個過濾器為每個請求創建鹽,另一個過濾器進行驗證。 由于用戶請求以及隨后應驗證的POST或GET不一定按順序執行,因此我決定使用基于時間的緩存來存儲有效鹽字符串列表。
用于為請求生成新的鹽并將其存儲在緩存中的第一個過濾器可以編碼如下:
package com.ricardozuasti.csrf;import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.RandomStringUtils;public class LoadSalt implements Filter {@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {// Assume its HTTPHttpServletRequest httpReq = (HttpServletRequest) request;// Check the user session for the salt cache, if none is present we create oneCache<String, Boolean> csrfPreventionSaltCache = (Cache<String, Boolean>)httpReq.getSession().getAttribute("csrfPreventionSaltCache");if (csrfPreventionSaltCache == null){csrfPreventionSaltCache = CacheBuilder.newBuilder().maximumSize(5000).expireAfterWrite(20, TimeUnit.MINUTES).build();httpReq.getSession().setAttribute("csrfPreventionSaltCache", csrfPreventionSaltCache);}// Generate the salt and store it in the users cacheString salt = RandomStringUtils.random(20, 0, 0, true, true, null, new SecureRandom());csrfPreventionSaltCache.put(salt, Boolean.TRUE);// Add the salt to the current request so it can be used// by the page rendered in this requesthttpReq.setAttribute("csrfPreventionSalt", salt);chain.doFilter(request, response);}@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic void destroy() {}
}
我使用Guava CacheBuilder創建鹽緩存,因為它既有大小限制,又有每個條目的過期超時。 為了生成實際的鹽,我使用了由Java 6 SecureRandom支持的Apache Commons RandomStringUtils ,以確保生成強大的種子。
在以AJAX鏈接,發布或調用安全交易的頁面結尾的所有請求中均應使用此過濾器,因此在大多數情況下,最好將其映射到每個請求(也許除了靜態內容(例如圖像) ,CSS等)。 您的web.xml中的映射應類似于:
...<filter><filter-name>loadSalt</filter-name><filter-class>com.ricardozuasti.csrf.LoadSalt</filter-class></filter>...<filter-mapping><filter-name>loadSalt</filter-name><url-pattern>*</url-pattern></filter-mapping>...
就像我說的,要在執行安全交易之前驗證鹽,我們可以編寫另一個過濾器:
package com.ricardozuasti.csrf;import com.google.common.cache.Cache;
import java.io.IOException;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;public class ValidateSalt implements Filter {@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {// Assume its HTTPHttpServletRequest httpReq = (HttpServletRequest) request;// Get the salt sent with the requestString salt = (String) httpReq.getParameter("csrfPreventionSalt");// Validate that the salt is in the cacheCache<String, Boolean> csrfPreventionSaltCache = (Cache<String, Boolean>)httpReq.getSession().getAttribute("csrfPreventionSaltCache");if (csrfPreventionSaltCache != null &&salt != null &&csrfPreventionSaltCache.getIfPresent(salt) != null){// If the salt is in the cache, we move onchain.doFilter(request, response);} else {// Otherwise we throw an exception aborting the request flowthrow new ServletException("Potential CSRF detected!! Inform a scary sysadmin ASAP.");}}@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic void destroy() {}
}
您應該為每個需要確保安全的請求(例如,檢索或修改敏感信息,轉移資金等)配置此過濾器,例如:
...<filter><filter-name>validateSalt</filter-name><filter-class>com.ricardozuasti.csrf.ValidateSalt</filter-class></filter>...<filter-mapping><filter-name>validateSalt</filter-name><url-pattern>/transferMoneyServlet</url-pattern></filter-mapping>...
配置兩個servlet后,所有受保護的請求都將失敗:)。 要解決此問題,您必須在每個以安全URL結尾的鏈接和表單帖子中添加csrfPreventionSalt參數,該參數包含具有相同名稱的request參數的值。 例如,在JSP頁面內以HTML形式:
...
<form action="/transferMoneyServlet" method="get"><input type="hidden" name="csrfPreventionSalt" value="<c:out value='${csrfPreventionSalt}'/>"/>...
</form>
...
當然,您可以編寫一個自定義標簽,一個不錯的Javascript代碼,或在每個所需的鏈接/表單中添加新參數的方法。
參考: Ricardo Zuasti博客博客上的JCG合作伙伴 Ricardo Zuasti 阻止了Java Web應用程序中的CSRF 。
翻譯自: https://www.javacodegeeks.com/2012/06/preventing-csrf-in-java-web-apps.html