跨站点请求伪造攻击(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