下文来源于SpringSecurity官方文档。
CSRF 攻击
在讨论 Spring Security 如何保护应用程序免受 CSRF 攻击之前,我们将解释什么是 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
现在,Feign 您对银行的网站进行身份验证,然后在不注销的情况下访问一个邪恶的网站。恶意网站包含具有以下格式的 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 仍与请求一起发送。
同步器令牌模式
问题在于,来自银行网站的 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 参数的正确值(必须在邪恶网站上明确提供),并且当服务器将实际令牌与预期令牌进行比较时,传输将失败。
何时使用 CSRF 保护
什么时候应该使用 CSRF 保护?我们的建议是对普通用户可能由浏览器处理的任何请求使用 CSRF 保护。如果仅创建非浏览器 Client 端使用的服务,则可能需要禁用 CSRF 保护。
CSRF 保护和 JSON
一个常见的问题是“我需要保护由 javascript 发出的 JSON 请求吗?”简短的答案是,这取决于。但是,您必须非常小心,因为有些 CSRF 漏洞会影响 JSON 请求。例如,恶意用户可以创建使用以下格式的带有 JSON 的 CSRF:
<form action="https://bank.example.com/transfer" method="post" enctype="text/plain">
<input name='{"amount":100,"routingNumber":"evilsRoutingNumber","account":"evilsAccountNumber", "ignore_me":"' value='test"}' type='hidden'>
<input type="submit"
value="Win Money!"/>
</form>
这将产生以下 JSON 结构
{ "amount": 100,
"routingNumber": "evilsRoutingNumber",
"account": "evilsAccountNumber",
"ignore_me": "=test"
}
如果应用程序未验证 Content-Type,则该应用程序将被暴露。根据设置的不同,仍然可以通过更新 URL 后缀以“ .json”结尾来利用可验证 Content Type 的 Spring MVC 应用程序,如下所示:
<form action="https://bank.example.com/transfer.json" method="post" enctype="text/plain">
<input name='{"amount":100,"routingNumber":"evilsRoutingNumber","account":"evilsAccountNumber", "ignore_me":"' value='test"}' type='hidden'>
<input type="submit"
value="Win Money!"/>
</form>
CSRF 和 Stateless 浏览器应用程序
如果我的应用程序是 Stateless 的怎么办?那并不一定意味着您受到保护。实际上,如果用户不需要针对给定请求在 Web 浏览器中执行任何操作,则他们可能仍然容易受到 CSRF 攻击。
例如,考虑一个应用程序使用一个包含其内所有状态的自定义 cookie 而不是 JSESSIONID 进行身份验证。进行 CSRF 攻击后,自定义 cookie 将与请求一起发送,其方式与在前面的示例中发送 JSESSIONID cookie 相同。
使用基本身份验证的用户也容易受到 CSRF 攻击,因为浏览器将在所有请求中自动包括用户名密码,就像在前面的示例中发送 JSESSIONID cookie 一样。