如今,随着RESTful架构变得越来越标准,可能值得花一些时间重新考虑当前的安全方法。 在这个小系列的博客文章中,我们将探索一些以无状态方式解决与Web相关的安全问题的相对较新的方法。 这第一篇文章是关于保护您的网站免受跨站请求伪造(CSRF)的攻击。
总结:什么是跨站点伪造?
CSRF攻击基于挥之不去的身份验证Cookie。 在登录或以其他方式标识为网站上的唯一访问者之后,该网站可能会在浏览器中留下cookie。 如果不显式注销或以其他方式删除此cookie,它可能会保持一段时间有效。
另一个站点可以通过使浏览器向受攻击的站点发出(跨站点)请求来滥用此功能。 例如,包括一些用于在“ http://siteunderattack.com/changepassword?pw=hacked”标签上进行POST的Javascript,将使浏览器发出该请求,并将对该域仍然有效的任何(身份验证)cookie附加到该请求!
即使单源策略(SOP)不允许恶意站点访问响应的任何部分。 从上面的示例中可以很明显地看出,如果请求的URL在后台触发任何副作用(状态更改),则损害已经完成。
常用方法
常用的解决方案是引入所谓的共享秘密CSRF令牌的要求,并将其作为先前响应的一部分让客户端知道。
然后,对于任何有副作用的请求,客户端都必须将其ping回服务器。 可以直接在表单中作为隐藏字段或作为自定义HTTP标头完成此操作。 无论哪种方式,其他站点都无法成功产生包含正确CSRF令牌的请求,因为SOP阻止跨站点读取来自服务器的响应。 这种方法的问题在于服务器需要记住会话中每个用户的每个CSRF令牌的值。
无状态方法
1.切换到完整且设计正确的基于JSON的REST API。
单源策略仅允许跨站点的HEAD / GET和POST。 POST只能是以下哑剧类型之一:application / x-www-form-urlencoded,multipart / form-data或text / plain。 确实没有JSON! 现在考虑到GET永远不要在任何经过适当设计的基于HTTP的API中触发副作用,这让您可以简单地禁止任何非JSON POST / PUT / DELETE,一切都很好。 对于上传文件(多部分/表单数据)的方案,仍然需要明确的CSRF保护。
2.检查HTTP Referer标头。
通过检查仍然易受攻击的场景(例如多部分/表单数据POST)的Referer标头的存在和内容,可以进一步完善上述方法。 浏览器使用此标头来指定触发请求的确切页面(url)。 这可以轻松地用于检查站点的预期域。 请注意,如果选择进行此类检查,则在没有标题的情况下,切勿允许请求。
3.客户端生成的CSRF令牌。
让客户端在Cookie和自定义HTTP标头中生成并发送相同的唯一秘密值。 考虑到仅允许网站为其自己的域读取/写入Cookie,因此只有真实网站才能在两个标头中发送相同的值。 使用这种方法,您的服务器要做的就是在每个请求无状态的基础上检查两个值是否相等!
实作
着眼于第三种基于显式但基于无状态CSRF令牌的安全性的方法,让我们看看使用Spring Boot和Spring Security的代码的外观。
在Spring Boot中,您会获得一些不错的默认安全设置,您可以使用自己的配置适配器对其进行微调。 在这种情况下,所需要做的就是禁用默认的csrf行为并添加自己的StatelessCSRFFilter:
自定义CSRF保护
@EnableWebSecurity
@Order(1)
public class StatelessCSRFSecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable().addFilterBefore(new StatelessCSRFFilter(), CsrfFilter.class);}
}
这是StatelessCSRFFilter的实现:
自定义CSRF过滤器
public class StatelessCSRFFilter extends OncePerRequestFilter {private static final String CSRF_TOKEN = "CSRF-TOKEN";private static final String X_CSRF_TOKEN = "X-CSRF-TOKEN";private final RequestMatcher requireCsrfProtectionMatcher = new DefaultRequiresCsrfMatcher();private final AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl();@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)throws ServletException, IOException {if (requireCsrfProtectionMatcher.matches(request)) {final String csrfTokenValue = request.getHeader(X_CSRF_TOKEN);final Cookie[] cookies = request.getCookies();String csrfCookieValue = null;if (cookies != null) {for (Cookie cookie : cookies) {if (cookie.getName().equals(CSRF_TOKEN)) {csrfCookieValue = cookie.getValue();}}}if (csrfTokenValue == null || !csrfTokenValue.equals(csrfCookieValue)) {accessDeniedHandler.handle(request, response, new AccessDeniedException("Missing or non-matching CSRF-token"));return;}}filterChain.doFilter(request, response);}public static final class DefaultRequiresCsrfMatcher implements RequestMatcher {private final Pattern allowedMethods = Pattern.compile("^(GET|HEAD|TRACE|OPTIONS)$");@Overridepublic boolean matches(HttpServletRequest request) {return !allowedMethods.matcher(request.getMethod()).matches();}}
}
不出所料,无状态版本在两个标头值上只做一个简单的equals()。
客户端实施
客户端实现也很简单,尤其是在使用AngularJS时。 AngularJS已经提供了内置的CSRF令牌支持。 如果您告诉它要读取的cookie,它将自动将其值发送到您选择的自定义标头中。 (浏览器负责发送cookie标头本身。)
您可以按以下方式覆盖AngularJS的默认名称(XSRF而不是CSRF):
设置适当的令牌名称
$http.defaults.xsrfHeaderName = 'X-CSRF-TOKEN';
$http.defaults.xsrfCookieName = 'CSRF-TOKEN';
此外,如果您想为每个请求生成一个新的令牌值,则可以将自定义拦截器添加到$ httpProvider中,如下所示:
拦截器生成cookie
app.config(['$httpProvider', function($httpProvider) {//fancy random token, losely after https://gist.github.com/jed/982883function b(a){return a?(a^Math.random()*16>>a/4).toString(16):([1e16]+1e16).replace(/[01]/g,b)};$httpProvider.interceptors.push(function() {return {'request': function(response) {// put a new random secret into our CSRF-TOKEN Cookie before each requestdocument.cookie = 'CSRF-TOKEN=' + b();return response;}};});
}]);
您可以在github上找到一个完整的可用示例。
确保已安装gradle 2.0,并使用“ gradle build”和“ gradle run”简单地运行它。 如果要像eclipse一样在IDE中使用它,请使用“ gradle eclipse”,只需从IDE内导入并运行它即可(无需服务器)。
免责声明
有时,经典的CSRF令牌被错误地视为针对重播或暴力攻击的解决方案。 此处列出的无状态方法未涵盖此类攻击。 我个人认为这两种类型的攻击都应从另一个角度进行考虑,例如使用https和速率限制。 对于公开网站上的任何数据输入,我俩都认为这是必须的!
翻译自: https://www.javacodegeeks.com/2014/10/stateless-spring-security-part-1-stateless-csrf-protection.html