领先一步
VMware 提供培训和认证,助您快速提升。
了解更多[提示 标题=更新]
此博客文章不再维护。请参考 CSRF 文档 获取有关 Spring Security 和 CSRF 防护的最新信息。
[/提示]周一,我宣布了 Spring Security 3.2.0.RC1 的发布。这是关于 Spring Security 3.2.0.RC1 中新增功能的两部分博客系列的第一部分。
在本篇博文中,我将介绍 Spring Security 的 CSRF 支持。在 下一篇文章 中,我将介绍已添加的各种安全标头。
Spring Security 添加了对 跨站请求伪造 (CSRF) 攻击 的防护。很好,但什么是 CSRF 攻击,Spring Security 如何保护我免受其侵害?让我们来看一个具体的例子来更好地理解。
假设您的银行网站提供了一个表单,允许将当前登录用户的资金转账到另一个银行账户。例如,HTTP 请求可能如下所示
POST /transfer HTTP/1.1
Host: bank.example.com
Cookie: JSESSIONID=randomid; Domain=bank.example.com; Secure; HttpOnly
Content-Type: application/x-www-form-urlencoded
amount=100.00&routingNumber=1234&account=9876
现在假设您已登录银行网站,然后在未注销的情况下访问了一个恶意网站。该恶意网站包含一个包含以下表单的 HTML 页面
<form action="https://bank.example.com/transfer" method="post">
<input type="hidden"
name="amount"
value="100.00"/>
<input type="hidden"
name="routingNumber"
value="evilsRoutingNumber"/>
<input type="hidden"
name="account"
value="evilsAccountNumber"/>
<input type="submit"
value="Win Money!'/>
</form>
您想赢钱,因此您点击了提交按钮。在此过程中,您无意中将 100 美元转账给了恶意用户。发生这种情况是因为,虽然恶意网站无法查看您的 Cookie,但与您的银行关联的 Cookie 仍然会与请求一起发送。
更糟糕的是,整个过程可以使用 JavaScript 自动化。这意味着您甚至不需要点击按钮。那么我们如何保护自己免受此类攻击呢?
问题在于,来自银行网站的 HTTP 请求和来自恶意网站的请求完全相同。这意味着无法拒绝来自恶意网站的请求并允许来自银行网站的请求。为了防止 CSRF 攻击,我们需要确保请求中包含恶意网站无法提供的内容。
一种解决方案是使用 同步器令牌模式。此解决方案是为了确保每个请求除了我们的会话 Cookie 之外,还需要一个随机生成的令牌作为 HTTP 参数。提交请求时,服务器必须查找参数的预期值并将其与请求中的实际值进行比较。如果值不匹配,则请求应失败。
我们可以放宽期望,仅要求每个更新状态的 HTTP 请求都包含令牌。这可以安全地完成,因为 同源策略 确保恶意网站无法读取响应。此外,我们不希望在 HTTP GET 中包含随机令牌,因为这可能 导致令牌泄露。
让我们看看我们的示例将如何变化。假设随机生成的令牌存在于名为 _csrf 的 HTTP 参数中。例如,转账请求将如下所示
POST /transfer HTTP/1.1
Host: bank.example.com
Cookie: JSESSIONID=randomid; Domain=bank.example.com; Secure; HttpOnly
Content-Type: application/x-www-form-urlencoded
amount=100.00&routingNumber=1234&account=9876&_csrf=<secure-random>
您会注意到,我们添加了带有随机值的 _csrf
参数。现在,恶意网站将无法猜测 _csrf 参数的正确值(必须在恶意网站上显式提供),并且当服务器将实际令牌与预期令牌进行比较时,转账将失败。
那么使用 Spring Security 来保护我们的网站免受 CSRF 攻击需要哪些步骤呢?使用 Spring Security 的 CSRF 防护的步骤如下所示
防止 CSRF 攻击的第一步是确保您的网站使用正确的 HTTP 方法。具体来说,在 Spring Security 的 CSRF 支持发挥作用之前,您需要确保您的应用程序正在使用 PATCH、POST、PUT 和/或 DELETE 来修改任何状态。这不是 Spring Security 支持的限制,而是正确防止 CSRF 的一般要求。
下一步是在您的应用程序中包含 Spring Security 的 CSRF 防护。如果您使用 XML 配置,则可以使用 <csrf />
元素
<http ...>
...
<csrf />
</http>
使用 Java 配置时,默认情况下启用 CSRF 防护。如果您想禁用 CSRF,则可以在下面看到相应的 Java 配置
@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
...;
}
}
如果您使用 Spring MVC form:form 标签,则会使用 CsrfRequestDataValueProcessor 自动为您包含 CsrfToken。
同样值得注意的是,一旦 问题 7 得到解决,Thymeleaf 应该会自动集成。
最后一步是确保您在所有 PATCH、POST、PUT 和 DELETE 方法中都包含 CSRF 令牌。这可以通过使用 _csrf 请求属性来获取当前的 CsrfToken 来实现。下面显示了使用 JSP 执行此操作的示例
<c:url var="logoutUrl" value="/logout"/>
<form action="${logoutUrl}"
method="post">
<input type="submit"
value="Log out" />
<input type="hidden"
name="${_csrf.parameterName}"
value="${_csrf.token}"/>
</form>
如果您使用 JSON,则无法在 HTTP 参数中提交 CSRF 令牌。相反,您可以在 HTTP 标头中提交令牌。一个典型的模式是在您的元标记中包含 CSRF 令牌
<html>
<head>
<meta name="_csrf" content="${_csrf.token}"/>
<!-- default header name is X-CSRF-TOKEN -->
<meta name="_csrf_header" content="${_csrf.headerName}"/>
...
</head>
...
然后,您可以在所有 AJAX 请求中包含该令牌。如果您使用的是 JQuery,则可以使用以下方法:
$(function () {
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
$(document).ajaxSend(function(e, xhr, options) {
xhr.setRequestHeader(header, token);
});
});
在实现 CSRF 时,有一些需要注意的事项。
Cookie 中的 CSRF
有人可能会问,为什么 CsrfToken 不存储在 Cookie 中?这是因为存在一些已知的漏洞,其他域可以设置 Cookie(即设置 Cookie)。另一个缺点是,通过移除状态(即超时),您将失去在令牌被泄露时强制终止令牌的能力。
一个问题是,预期的 CSRF 令牌存储在 HttpSession 中,因此,一旦 HttpSession 过期,您配置的 AccessDeniedHandler 将会收到一个 InvalidCsrfTokenException
。如果您使用的是默认的 AccessDeniedHandler
,浏览器将收到 HTTP 403 并显示一条不友好的错误消息。
缓解活跃用户遇到超时问题的一个简单方法是使用一些 JavaScript 代码,让用户知道他们的会话即将过期。用户可以点击一个按钮继续并刷新会话。
或者,指定一个自定义的 AccessDeniedHandler,允许您按照自己的方式处理 InvalidCsrfTokenException
。有关如何自定义 AccessDeniedHandler 的示例,请参阅为 xml 和 Java 配置 提供的链接。
为了防止 伪造登录请求,登录表单也应该受到 CSRF 攻击的保护。由于 CsrfToken 存储在 HttpSession 中,这意味着将立即创建一个 HttpSession。虽然这在 RESTful/无状态架构中听起来很糟糕,但实际上,状态对于实现实用的安全至关重要。如果没有状态,如果令牌被泄露,我们将无能为力。实际上,CSRF 令牌的大小非常小,对我们架构的影响可以忽略不计。
添加 CSRF 将更新 LogoutFilter,使其仅使用 HTTP POST。这确保注销需要 CSRF 令牌,恶意用户无法强制注销您的用户。
一种方法是使用表单进行注销。如果您确实需要链接,可以使用 JavaScript 让链接执行 POST(例如,可能在隐藏表单上)。对于 JavaScript 禁用的浏览器,您可以选择让链接将用户带到一个注销确认页面,该页面将执行 POST。
HiddenHttpMethodFilter
应该放在 Spring Security 过滤器之前。一般来说这是正确的,但在防止 CSRF 攻击时可能会产生其他影响。
请注意,HiddenHttpMethodFilter 仅在 POST 上覆盖 HTTP 方法,因此实际上不太可能导致任何实际问题。但是,将其放在 Spring Security 过滤器之前仍然是最佳实践。
Spring Security 的目标是提供保护用户免受漏洞利用的默认值。这并不意味着您被迫接受其所有默认值。
例如,您可以提供一个自定义的 CsrfTokenRepository 来覆盖 CsrfToken 的存储方式。
您还可以指定一个自定义的 RequestMatcher 来确定哪些请求受到 CSRF 的保护(例如,您可能不关心注销是否被利用)。简而言之,如果 Spring Security 的 CSRF 保护行为与您期望的不符,您可以自定义其行为。
现在您应该对 CSRF 是什么以及如何使用 Spring Security 来保护您的应用程序免受 CSRF 漏洞利用有了很好的了解。
在 下一篇文章中,我将讨论如何使用 Spring Security 的标头支持来保护您的应用程序免受点击劫持等漏洞利用。