对于最新的稳定版本,请使用 Spring Security 6.3.1Spring中文文档

对于最新的稳定版本,请使用 Spring Security 6.3.1Spring中文文档

Spring 为防范跨站请求伪造 (CSRF) 攻击提供了全面的支持。 在以下各节中,我们将探讨:Spring中文文档

文档的这一部分讨论了 CSRF 保护的一般主题。 有关基于 servletWebFlux 的应用程序的 CSRF 保护的具体信息,请参阅相关部分。Spring中文文档

文档的这一部分讨论了 CSRF 保护的一般主题。 有关基于 servletWebFlux 的应用程序的 CSRF 保护的具体信息,请参阅相关部分。Spring中文文档

什么是CSRF攻击?

了解 CSRF 攻击的最佳方法是查看一个具体示例。Spring中文文档

假设您的银行网站提供了一个表格,允许将资金从当前登录的用户转移到另一个银行账户。 例如,转移表单可能如下所示:Spring中文文档

转让表格
<form method="post"
	action="/transfer">
<input type="text"
	name="amount"/>
<input type="text"
	name="routingNumber"/>
<input type="text"
	name="account"/>
<input type="submit"
	value="Transfer"/>
</form>

相应的 HTTP 请求可能如下所示:Spring中文文档

传输 HTTP 请求
POST /transfer HTTP/1.1
Host: bank.example.com
Cookie: JSESSIONID=randomid
Content-Type: application/x-www-form-urlencoded

amount=100.00&routingNumber=1234&account=9876

现在假装您对银行的网站进行了身份验证,然后在不注销的情况下访问了一个邪恶的网站。 邪恶的网站包含一个格式如下的 HTML 页面:Spring中文文档

邪恶转移表格
<form method="post"
	action="https://bank.example.com/transfer">
<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 仍会与请求一起发送。Spring中文文档

更糟糕的是,整个过程本可以通过使用 JavaScript 来自动化。 这意味着您甚至不需要单击按钮。 此外,在访问成为 XSS 攻击受害者的诚实网站时,它也很容易发生。 那么,我们如何保护我们的用户免受此类攻击呢?Spring中文文档

防范 CSRF 攻击

CSRF攻击之所以可能发生,是因为来自受害者网站的HTTP请求和来自攻击者网站的请求完全相同。 这意味着无法拒绝来自邪恶网站的请求,而只允许来自银行网站的请求。 为了防止 CSRF 攻击,我们需要确保请求中存在恶意站点无法提供的内容,以便我们可以区分这两个请求。Spring中文文档

Spring 提供了两种机制来防止 CSRF 攻击:Spring中文文档

这两种保护都要求安全方法是幂等的。Spring中文文档

安全方法必须是幂等的

要使针对 CSRF 的保护工作正常,应用程序必须确保“安全”HTTP 方法是幂等的。 这意味着使用 HTTP 、 、 和 方法的请求不应更改应用程序的状态。GETHEADOPTIONSTRACESpring中文文档

同步器令牌模式

防范 CSRF 攻击的主要和最全面的方法是使用 Synchronizer 令牌模式。 此解决方案旨在确保每个 HTTP 请求除了我们的会话 cookie 之外,还需要在 HTTP 请求中存在一个称为 CSRF 令牌的安全随机生成值。Spring中文文档

提交 HTTP 请求时,服务器必须查找预期的 CSRF 令牌,并将其与 HTTP 请求中的实际 CSRF 令牌进行比较。 如果值不匹配,则应拒绝 HTTP 请求。Spring中文文档

这项工作的关键是,实际的 CSRF 令牌应该位于 HTTP 请求的一部分中,该请求不会被浏览器自动包含。 例如,在 HTTP 参数或 HTTP 标头中要求实际的 CSRF 令牌将防止 CSRF 攻击。 在 cookie 中要求实际的 CSRF 令牌不起作用,因为浏览器会自动将 cookie 包含在 HTTP 请求中。Spring中文文档

我们可以放宽期望,只要求每个更新应用程序状态的 HTTP 请求的实际 CSRF 令牌。 为了实现这一点,我们的应用程序必须确保安全的 HTTP 方法是幂等的。 这提高了可用性,因为我们希望允许从外部网站链接到我们的网站。 此外,我们不希望在 HTTP GET 中包含随机令牌,因为这可能会导致令牌泄露。Spring中文文档

考虑一下,当我们使用 Synchronizer Token Pattern 时,我们的示例将如何变化。 假设实际的 CSRF 令牌需要位于名为 的 HTTP 参数中。 我们申请的转移表格如下所示:_csrfSpring中文文档

同步器令牌形式
<form method="post"
	action="/transfer">
<input type="hidden"
	name="_csrf"
	value="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
<input type="text"
	name="amount"/>
<input type="text"
	name="routingNumber"/>
<input type="hidden"
	name="account"/>
<input type="submit"
	value="Transfer"/>
</form>

该表单现在包含一个隐藏的输入,其中包含 CSRF 令牌的值。 外部站点无法读取 CSRF 令牌,因为同源策略可确保恶意站点无法读取响应。Spring中文文档

相应的转账 HTTP 请求如下所示:Spring中文文档

同步器令牌请求
POST /transfer HTTP/1.1
Host: bank.example.com
Cookie: JSESSIONID=randomid
Content-Type: application/x-www-form-urlencoded

amount=100.00&routingNumber=1234&account=9876&_csrf=4bfd1575-3ad1-4d21-96c7-4ef2d9f86721

您会注意到 HTTP 请求现在包含具有安全随机值的参数。 邪恶网站将无法为参数提供正确的值(必须在邪恶网站上明确提供),并且当服务器将实际的 CSRF 令牌与预期的 CSRF 令牌进行比较时,传输将失败。_csrf_csrfSpring中文文档

SameSite 属性

防止 CSRF 攻击的一种新兴方法是在 Cookie 上指定 SameSite 属性。 服务器可以在设置 cookie 时指定属性,以指示来自外部站点时不应发送 cookie。SameSiteSpring中文文档

Spring Security 不直接控制会话 cookie 的创建,因此它不提供对 SameSite 属性的支持。Spring Session 在基于 servlet 的应用程序中提供对该属性的支持。 Spring Framework 的 CookieWebSessionIdResolver 在基于 WebFlux 的应用程序中为该属性提供了开箱即用的支持。SameSiteSameSiteSpring中文文档

具有该属性的 HTTP 响应标头的示例可能如下所示:SameSiteSpring中文文档

SameSite HTTP 响应
Set-Cookie: JSESSIONID=randomid; Domain=bank.example.com; Secure; HttpOnly; SameSite=Lax

该属性的有效值为:SameSiteSpring中文文档

考虑如何使用该属性保护我们的示例。 银行应用程序可以通过在会话 cookie 上指定属性来防止 CSRF。SameSiteSameSiteSpring中文文档

在我们的会话 cookie 上设置属性后,浏览器会继续发送来自银行网站的请求的 cookie。 但是,浏览器不再发送带有来自邪恶网站的传输请求的 cookie。 由于来自邪恶网站的传输请求中不再存在会话,因此该应用程序受到保护,免受 CSRF 攻击。SameSiteJSESSIONIDJSESSIONIDSpring中文文档

使用属性来防止 CSRF 攻击时,需要注意一些重要的注意事项SameSiteSpring中文文档

将属性设置为可提供更强的防御,但可能会使用户感到困惑。 考虑一个用户,他保持登录状态,访问托管在 social.example.com 的社交媒体网站。 用户在 email.example.org 收到一封电子邮件,其中包含指向社交媒体网站的链接。 如果用户点击该链接,他们理所当然地希望获得社交媒体网站的身份验证。 但是,如果属性为 ,则不会发送 cookie,因此不会对用户进行身份验证。SameSiteStrictSameSiteStrictSpring中文文档

我们可以通过实施 gh-7537 来提高对 CSRF 攻击的保护和可用性。SameSiteSpring中文文档

另一个明显的考虑因素是,为了使该属性保护用户,浏览器必须支持该属性。 大多数现代浏览器都支持 SameSite 属性。 但是,仍在使用的旧浏览器可能不会。SameSiteSameSiteSpring中文文档

出于这个原因,我们通常建议使用该属性作为纵深防御,而不是针对 CSRF 攻击的唯一保护。SameSiteSpring中文文档

这两种保护都要求安全方法是幂等的。Spring中文文档

Spring Security 不直接控制会话 cookie 的创建,因此它不提供对 SameSite 属性的支持。Spring Session 在基于 servlet 的应用程序中提供对该属性的支持。 Spring Framework 的 CookieWebSessionIdResolver 在基于 WebFlux 的应用程序中为该属性提供了开箱即用的支持。SameSiteSameSiteSpring中文文档

我们可以通过实施 gh-7537 来提高对 CSRF 攻击的保护和可用性。SameSiteSpring中文文档

何时使用 CSRF 保护

何时应使用 CSRF 保护? 我们的建议是将 CSRF 保护用于普通用户可能由浏览器处理的任何请求。 如果要创建仅供非浏览器客户端使用的服务,则可能需要禁用 CSRF 保护。Spring中文文档

CSRF 保护和 JSON

一个常见的问题是“我需要保护 JavaScript 发出的 JSON 请求吗? 简短的回答是:视情况而定。 但是,您必须非常小心,因为存在可能会影响 JSON 请求的 CSRF 漏洞。 例如,恶意用户可以使用以下表单创建带有 JSON 的 CSRFSpring中文文档

带有 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 结构Spring中文文档

带有 JSON 请求的 CSRF
{ "amount": 100,
"routingNumber": "evilsRoutingNumber",
"account": "evilsAccountNumber",
"ignore_me": "=test"
}

如果应用程序未验证标头,则会暴露于此漏洞。 根据设置,验证 Content-Type 的 Spring MVC 应用程序仍可以通过将 URL 后缀更新为 结尾来利用,如下所示:Content-Type.jsonSpring中文文档

带有 JSON Spring MVC 表单的 CSRF
<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 和无状态浏览器应用程序

如果我的申请是无状态的怎么办? 这并不一定意味着您受到保护。 事实上,如果用户不需要在 Web 浏览器中对给定请求执行任何操作,他们可能仍然容易受到 CSRF 攻击。Spring中文文档

例如,假设一个应用程序使用自定义 cookie,该 cookie 包含其中用于身份验证的所有状态(而不是 JSESSIONID)。 当进行 CSRF 攻击时,自定义 cookie 会以与上一个示例中发送 JSESSIONID cookie 相同的方式与请求一起发送。 此应用程序容易受到 CSRF 攻击。Spring中文文档

使用基本身份验证的应用程序也容易受到 CSRF 攻击。 该应用程序容易受到攻击,因为浏览器会自动在任何请求中包含用户名和密码,其方式与上一个示例中发送 JSESSIONID cookie 的方式相同。Spring中文文档

CSRF 注意事项

在实施针对 CSRF 攻击的防护时,需要考虑一些特殊注意事项。Spring中文文档

登录

为了防止伪造登录请求,应保护登录 HTTP 请求免受 CSRF 攻击。 防止伪造登录请求是必要的,这样恶意用户就无法读取受害者的敏感信息。 攻击的执行方式如下:Spring中文文档

  1. 恶意用户使用恶意用户的凭据执行 CSRF 登录。 受害者现在被认证为恶意用户。Spring中文文档

  2. 然后,恶意用户诱骗受害者访问受感染的网站并输入敏感信息。Spring中文文档

  3. 该信息与恶意用户的帐户相关联,因此恶意用户可以使用自己的凭据登录并查看受害者的敏感信息。Spring中文文档

确保登录 HTTP 请求免受 CSRF 攻击的一个可能复杂因素是,用户可能会遇到会话超时,导致请求被拒绝。 对于不希望需要会话登录的用户来说,会话超时是令人惊讶的。 有关详细信息,请参阅 CSRF 和会话超时Spring中文文档

注销

为了防止伪造注销请求,应保护注销 HTTP 请求免受 CSRF 攻击。 防止伪造注销请求是必要的,这样恶意用户就无法读取受害者的敏感信息。 有关攻击的详细信息,请参阅此博客文章Spring中文文档

确保注销 HTTP 请求免受 CSRF 攻击的一个可能复杂因素是,用户可能会遇到会话超时,导致请求被拒绝。 会话超时对于不希望注销会话的用户来说是令人惊讶的。 有关详细信息,请参阅 CSRF 和会话超时Spring中文文档

CSRF 和会话超时

通常情况下,预期的 CSRF 令牌存储在会话中。 这意味着,一旦会话过期,服务器就找不到预期的 CSRF 令牌并拒绝 HTTP 请求。 有许多选项(每个选项都有权衡)来解决超时问题:Spring中文文档

  • 缓解超时的最佳方法是使用 JavaScript 在表单提交时请求 CSRF 令牌。 然后,使用 CSRF 令牌更新表单并提交。Spring中文文档

  • 另一种选择是使用一些 JavaScript,让用户知道他们的会话即将到期。 用户可以单击按钮继续并刷新会话。Spring中文文档

  • 最后,预期的 CSRF 令牌可以存储在 cookie 中。 这样,预期的 CSRF 令牌在会话中存活。Spring中文文档

    有人可能会问,为什么默认情况下预期的 CSRF 令牌不存储在 cookie 中。 这是因为存在已知的漏洞,其中标头(例如,指定 cookie)可以由另一个域设置。 这与 Ruby on Rails 在标头 X-Requested-With 存在时不再跳过 CSRF 检查的原因相同。 有关如何执行漏洞利用的详细信息,请参阅此 webappsec.org 线程。 另一个缺点是,通过删除状态(即超时),您将失去在令牌遭到入侵时强制使令牌失效的能力。Spring中文文档

分片(文件上传)

保护分段请求(文件上传)免受 CSRF 攻击会导致先有鸡还是先有蛋的问题。 为了防止 CSRF 攻击的发生,必须读取 HTTP 请求的正文以获取实际的 CSRF 令牌。 但是,读取正文意味着文件已上传,这意味着外部站点可以上传文件。Spring中文文档

将 CSRF 保护与 multipart/form-data 一起使用有两个选项:Spring中文文档

每个选项都有其权衡取舍。Spring中文文档

在将 Spring Security 的 CSRF 保护与分段文件上传集成之前,应首先确保可以在没有 CSRF 保护的情况下进行上传。 有关在 Spring 中使用多部分表单的更多信息,请参阅 1.1.11。Spring 参考的 Multipart Resolver 部分和 MultipartFilter JavadocSpring中文文档

将 CSRF 令牌放在正文中

第一种选择是在请求正文中包含实际的 CSRF 令牌。 通过将 CSRF 令牌放在正文中,在执行授权之前读取正文。 这意味着任何人都可以在您的服务器上放置临时文件。 但是,只有授权用户才能提交由您的应用程序处理的文件。 通常,这是推荐的方法,因为临时文件上传对大多数服务器的影响可以忽略不计。Spring中文文档

在 URL 中包含 CSRF 令牌

如果不允许未经授权的用户上传临时文件,则另一种方法是将预期的 CSRF 令牌作为查询参数包含在表单的 action 属性中。 此方法的缺点是查询参数可能会泄露。 更一般地说,最佳做法是将敏感数据放在正文或标头中,以确保其不会泄露。 可以在 RFC 2616 第 15.1.3 节 在 URI 中对敏感信息进行编码中找到其他信息。Spring中文文档

HiddenHttpMethodFilter

某些应用程序可以使用表单参数来重写 HTTP 方法。 例如,以下形式可以将 HTTP 方法视为 而不是 .deletepostSpring中文文档

CSRF 隐藏的 HTTP 方法表单
<form action="/process"
	method="post">
	<!-- ... -->
	<input type="hidden"
		name="_method"
		value="delete"/>
</form>

在筛选器中重写 HTTP 方法。 该过滤器必须放在 Spring Security 的支持之前。 请注意,覆盖只发生在 上,因此这实际上不太可能导致任何实际问题。 但是,最佳做法仍然是确保将其放置在 Spring Security 的过滤器之前。postSpring中文文档

在将 Spring Security 的 CSRF 保护与分段文件上传集成之前,应首先确保可以在没有 CSRF 保护的情况下进行上传。 有关在 Spring 中使用多部分表单的更多信息,请参阅 1.1.11。Spring 参考的 Multipart Resolver 部分和 MultipartFilter JavadocSpring中文文档