Cross Site Request Forgery (CSRF)

Spring 为抵御 Cross Site Request Forgery (CSRF)攻击提供了全面的支持。在以下部分中,我们探讨:

Spring provides comprehensive support for protecting against Cross Site Request Forgery (CSRF) attacks. In the following sections, we explore:

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

This portion of the documentation discusses the general topic of CSRF protection. See the relevant sections for specific information on CSRF protection for servlet and WebFlux based applications.

What is a CSRF Attack?

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

The best way to understand a CSRF attack is by taking a look at a concrete example.

假设你的银行网站提供了一个表单,该表单允许将钱从当前登录用户转账到另一个银行帐户。例如,转账表单可能如下所示:

Assume that your bank’s website provides a form that allows transferring money from the currently logged in user to another bank account. For example, the transfer form might look like:

Transfer form
<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 请求可能如下所示:

The corresponding HTTP request might look like:

Transfer HTTP request
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 页面,其中包含以下表单:

Now pretend you authenticate to your bank’s website and then, without logging out, visit an evil website. The evil website contains an HTML page with the following form:

Evil transfer form
<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 仍会随请求一起发送。

You like to win money, so you click on the submit button. In the process, you have unintentionally transferred $100 to a malicious user. This happens because, while the evil website cannot see your cookies, the cookies associated with your bank are still sent along with the request.

更糟糕的是,整个过程可以通过使用 JavaScript 自动化。这意味着你甚至不需要点击按钮。此外,当访问成为 XSS attack受害者的诚实站点时,也可能发生这种情况。那么我们如何保护我们的用户免受此类攻击?

Worse yet, this whole process could have been automated by using JavaScript. This means you did not even need to click on the button. Furthermore, it could just as easily happen when visiting an honest site that is a victim of a XSS attack. So how do we protect our users from such attacks?

Protecting Against CSRF Attacks

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

The reason that a CSRF attack is possible is that the HTTP request from the victim’s website and the request from the attacker’s website are exactly the same. This means there is no way to reject requests coming from the evil website and allow only requests coming from the bank’s website. To protect against CSRF attacks, we need to ensure there is something in the request that the evil site is unable to provide so we can differentiate the two requests.

Spring 提供两种机制来防范 CSRF 攻击:

Spring provides two mechanisms to protect against CSRF attacks:

两种保护都需要 Safe Methods be Read-only

Both protections require that csrf-protection-read-only.

Safe Methods Must be Read-only

要让 either protection对抗 CSRF 起作用,应用程序必须确保 "safe" HTTP methods are read-only。这意味着使用 HTTP GETHEAD、`OPTIONS`和 `TRACE`方法的请求不应改变应用程序的状态。

For csrf-protection against CSRF to work, the application must ensure that "safe" HTTP methods are read-only. This means that requests with the HTTP GET, HEAD, OPTIONS, and TRACE methods should not change the state of the application.

Synchronizer Token Pattern

抵御 CSRF 攻击的主要且最全面方式是使用 Synchronizer Token Pattern。此解决方案是为了确保除了我们的会话 Cookie 之外,每个 HTTP 请求都需要将一个称为 CSRF 令牌的安全随机生成值包含在 HTTP 请求中。

The predominant and most comprehensive way to protect against CSRF attacks is to use the Synchronizer Token Pattern. This solution is to ensure that each HTTP request requires, in addition to our session cookie, a secure random generated value called a CSRF token be present in the HTTP request.

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

When an HTTP request is submitted, the server must look up the expected CSRF token and compare it against the actual CSRF token in the HTTP request. If the values do not match, the HTTP request should be rejected.

此功能的关键在于,实际的 CRSF 令牌应位于 HTTP 请求中不会自动包含在内的部分中。例如,在 HTTP 参数或 HTTP 标头中要求实际 CRSF 令牌将保护免受 CRSF 攻击。在 cookie 中要求实际的 CRSF 令牌不起作用,因为浏览器会自动在 HTTP 请求中包含 cookie。

The key to this working is that the actual CSRF token should be in a part of the HTTP request that is not automatically included by the browser. For example, requiring the actual CSRF token in an HTTP parameter or an HTTP header will protect against CSRF attacks. Requiring the actual CSRF token in a cookie does not work because cookies are automatically included in the HTTP request by the browser.

我们可以放宽要求,仅对更新应用程序状态的每个 HTTP 请求要求实际的 CRSF 令牌。为此,我们的应用程序必须确保 safe HTTP methods are read-only。这提高了可用性,因为我们希望允许从外部站点链接到我们的网站。此外,我们不想在 HTTP GET 中包含随机令牌,因为这会导致令牌泄露。

We can relax the expectations to require only the actual CSRF token for each HTTP request that updates the state of the application. For that to work, our application must ensure that csrf-protection-read-only. This improves usability, since we want to allow linking to our website from external sites. Additionally, we do not want to include the random token in HTTP GET, as this can cause the tokens to be leaked.

考虑当我们使用 Synchronizer Token Pattern 时,our example 会如何改变。假设需要实际的 CRSF 令牌才能输入一个名为 _csrf 的 HTTP 参数。我们的应用程序的转移表单将如下所示:

Consider how csrf-explained would change when we use the Synchronizer Token Pattern. Assume that the actual CSRF token is required to be in an HTTP parameter named _csrf. Our application’s transfer form would look like:

Synchronizer Token Form
<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 令牌,因为同源策略确保恶意站点无法读取响应。

The form now contains a hidden input with the value of the CSRF token. External sites cannot read the CSRF token since the same origin policy ensures the evil site cannot read the response.

进行转账的相应 HTTP 请求看起来如下:

The corresponding HTTP request to transfer money would look like this:

Synchronizer Token request
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 令牌与预期的 CSRF 令牌进行比较时,转账将失败。

You will notice that the HTTP request now contains the _csrf parameter with a secure random value. The evil website will not be able to provide the correct value for the _csrf parameter (which must be explicitly provided on the evil website) and the transfer will fail when the server compares the actual CSRF token to the expected CSRF token.

SameSite Attribute

一种新兴的抵御 CSRF Attacks的方式是在 Cookie 中指定 SameSite Attribute。设置 Cookie 时,服务器可以指定 `SameSite`属性,以指示不应从外部站点发送 Cookie。

An emerging way to protect against csrf is to specify the SameSite Attribute on cookies. A server can specify the SameSite attribute when setting a cookie to indicate that the cookie should not be sent when coming from external sites.

Spring Security 不会直接控制会话 Cookie 的创建,因此它不支持 SameSite 属性。 Spring Session为基于 servlet 的应用程序提供了 SameSite`属性的支持。Spring 框架的 `CookieWebSessionIdResolver为基于 WebFlux 的应用程序提供了对 `SameSite`属性的开箱即用支持。

Spring Security does not directly control the creation of the session cookie, so it does not provide support for the SameSite attribute. Spring Session provides support for the SameSite attribute in servlet-based applications. Spring Framework’s CookieWebSessionIdResolver provides out of the box support for the SameSite attribute in WebFlux-based applications.

带有 SameSite 属性的 HTTP 响应标头的示例可能如下所示:

An example, of an HTTP response header with the SameSite attribute might look like:

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

SameSite 属性的有效值为:

Valid values for the SameSite attribute are:

  • Strict: When specified, any request coming from the same-site includes the cookie. Otherwise, the cookie is not included in the HTTP request.

  • Lax: When specified, cookies are sent when coming from the same-site or when the request comes from top-level navigations and the csrf-protection-read-only. Otherwise, the cookie is not included in the HTTP request.

考虑如何使用 SameSite 属性保护 our example。银行应用程序可以通过在会话 cookie 中指定 SameSite 属性,从而保护自己免遭 CRSF 攻击。

Consider how csrf-explained could be protected using the SameSite attribute. The bank application can protect against CSRF by specifying the SameSite attribute on the session cookie.

使用 SameSite 属性设置我们的会话 cookie 后,浏览器继续使用来自银行网站的请求发送 JSESSIONID cookie。但是,浏览器不再使用来自恶意网站的转账请求发送 JSESSIONID cookie。由于会话不再出现在来自恶意网站的转账请求中,因此可以保护应用程序免遭 CRSF 攻击。

With the SameSite attribute set on our session cookie, the browser continues to send the JSESSIONID cookie with requests coming from the banking website. However, the browser no longer sends the JSESSIONID cookie with a transfer request coming from the evil website. Since the session is no longer present in the transfer request coming from the evil website, the application is protected from the CSRF attack.

在使用 `SameSite`属性来抵御 CSRF 攻击时,有一些重要的 considerations需要注意。

There are some important considerations to be aware of when using SameSite attribute to protect against CSRF attacks.

SameSite`属性设置为 `Strict`提供了更强的防御,但可能会迷惑用户。考虑一下一个用户始终登录到托管在 [role="bare"][role="bare"]https://social.example.com的社交媒体网站上。该用户在 [role="bare"][role="bare"]https://email.example.org收到一封电子邮件,其中包含到社交媒体网站的链接。如果用户点击此链接,他们将有权被社交媒体网站验证身份。但是,如果 `SameSite`属性为 `Strict,则不会发送 Cookie,因此不会验证用户身份。

Setting the SameSite attribute to Strict provides a stronger defense but can confuse users. Consider a user who stays logged into a social media site hosted at [role="bare"]https://social.example.com. The user receives an email at [role="bare"]https://email.example.org that includes a link to the social media site. If the user clicks on the link, they would rightfully expect to be authenticated to the social media site. However, if the SameSite attribute is Strict, the cookie would not be sent and so the user would not be authenticated.

我们可以通过实现 gh-7537来提高 `SameSite`对 CSRF 攻击的保护和可用性。

We could improve the protection and usability of SameSite protection against CSRF attacks by implementing gh-7537.

另一个显而易见的考虑因素是,为了让 `SameSite`属性保护用户,浏览器必须支持 `SameSite`属性。大多数现代浏览器确实 support the SameSite attribute。然而,仍在使用的旧浏览器可能不支持。

Another obvious consideration is that, in order for the SameSite attribute to protect users, the browser must support the SameSite attribute. Most modern browsers do support the SameSite attribute. However, older browsers that are still in use may not.

因此,我们通常建议使用 SameSite 属性作为纵深防御,而不是对抗 CSRF 攻击的唯一保护措施。

For this reason, we generally recommend using the SameSite attribute as a defense in depth rather than the sole protection against CSRF attacks.

When to use CSRF protection

您应该在何时使用 CSRF 保护?我们的建议是,对所有可能由浏览器为普通用户处理的请求使用 CSRF 保护。如果您正在创建一个仅供非浏览器客户端使用的服务,那么您可能需要禁用 CSRF 保护。

When should you use CSRF protection? Our recommendation is to use CSRF protection for any request that could be processed by a browser by normal users. If you are creating a service that is used only by non-browser clients, you likely want to disable CSRF protection.

CSRF protection and JSON

一个常见的问题是 "`do I need to protect JSON requests made by JavaScript?`"简短的答案是:这取决于情况。但是,你必须非常小心,因为有一些 CSRF 漏洞会影响 JSON 请求。例如,恶意用户可以创建一个 CSRF with JSON by using the following form

A common question is “do I need to protect JSON requests made by JavaScript?” The short answer is: It depends. However, you must be very careful, as there are CSRF exploits that can impact JSON requests. For example, a malicious user can create a CSRF with JSON by using the following form:

CSRF with JSON form
<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 结构:

This produces the following JSON structure

CSRF with JSON request
{ "amount": 100,
"routingNumber": "evilsRoutingNumber",
"account": "evilsAccountNumber",
"ignore_me": "=test"
}

如果应用程序没有验证 Content-Type 标头,那么它就会面临此漏洞。基于安装情况,Spring MVC 应用程序可以通过将 URL 后缀更新为以 .json 结尾来利用验证 Content-Type 中的漏洞,如下所示:

If an application were not validating the Content-Type header, it would be exposed to this exploit. Depending on the setup, a Spring MVC application that validates the Content-Type could still be exploited by updating the URL suffix to end with .json, as follows:

CSRF with JSON Spring MVC form
<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 and Stateless Browser Applications

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

What if my application is stateless? That does not necessarily mean you are protected. In fact, if a user does not need to perform any actions in the web browser for a given request, they are likely still vulnerable to CSRF attacks.

例如,考虑使用自定义 Cookie 的应用程序,它在其内部包含用于身份验证的所有状态(而不是 JSESSIONID)。当进行 CSRF 攻击时,自定义 Cookie 会与在我们的前一个示例中发送的 JSESSIONID Cookie 以相同的方式随请求一起发送。此应用程序面临 CSRF 攻击的风险。

For example, consider an application that uses a custom cookie that contains all the state within it for authentication (instead of the JSESSIONID). When the CSRF attack is made, the custom cookie is sent with the request in the same manner that the JSESSIONID cookie was sent in our previous example. This application is vulnerable to CSRF attacks.

使用基本身份验证的应用程序也面临 CSRF 攻击的风险。此应用程序面临风险,因为浏览器自动在任何请求中包含用户名和密码,与在我们的前一个示例中发送 JSESSIONID Cookie 以相同的方式。

Applications that use basic authentication are also vulnerable to CSRF attacks. The application is vulnerable since the browser automatically includes the username and password in any requests in the same manner that the JSESSIONID cookie was sent in our previous example.

CSRF Considerations

实施针对 CSRF 攻击的保护时,有一些注意事项需要考虑。

There are a few special considerations to consider when implementing protection against CSRF attacks.

Logging In

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

To protect against forging login requests, the login HTTP request should be protected against CSRF attacks. Protecting against forging login requests is necessary so that a malicious user cannot read a victim’s sensitive information. The attack is performed as follows:

  1. A malicious user performs a CSRF login with the malicious user’s credentials. The victim is now authenticated as the malicious user.

  2. The malicious user then tricks the victim into visiting the compromised website and entering sensitive information.

  3. The information is associated to the malicious user’s account so the malicious user can log in with their own credentials and view the victim’s sensitive information.

确保 HTTP 登录请求受到 CSRF 攻击的保护可能会遇到的一个可能的麻烦是,用户可能遇到会话超时导致请求被拒绝的情况。对于原本不认为需要会话来登录的用户而言,会话超时是令人意外的。关于更多信息,请参阅 CSRF and Session Timeouts

A possible complication to ensuring login HTTP requests are protected against CSRF attacks is that the user might experience a session timeout that causes the request to be rejected. A session timeout is surprising to users who do not expect to need to have a session to log in. For more information refer to CSRF and Session Timeouts.

Logging Out

为了防止伪造注销请求,注销 HTTP 请求应受 CSRF 攻击保护。防止伪造注销请求是必要的,这样恶意用户就无法读取受害者的敏感信息。有关攻击的详细信息,请参见 this blog post

To protect against forging logout requests, the logout HTTP request should be protected against CSRF attacks. Protecting against forging logout requests is necessary so that a malicious user cannot read a victim’s sensitive information. For details on the attack, see this blog post.

确保 HTTP 注销请求受到 CSRF 攻击的保护可能会遇到的一个可能的麻烦是,用户可能遇到会话超时导致请求被拒绝的情况。对于原本不认为需要会话来注销的用户而言,会话超时是令人意外的。关于更多信息,请参见 CSRF and Session Timeouts

A possible complication to ensuring logout HTTP requests are protected against CSRF attacks is that the user might experience a session timeout that causes the request to be rejected. A session timeout is surprising to users who do not expect to have a session to log out. For more information, see CSRF and Session Timeouts.

CSRF and Session Timeouts

大多数情况下,预期的 CSRF 令牌会存储在会话中。这意味着,一旦会话过期,服务器便找不到预期的 CSRF 令牌并拒绝 HTTP 请求。有很多选项(每个选项都带有权衡利弊的情况)可以解决超时问题:

More often than not, the expected CSRF token is stored in the session. This means that, as soon as the session expires, the server does not find an expected CSRF token and rejects the HTTP request. There are a number of options (each of which come with trade offs) to solve timeouts:

  • The best way to mitigate the timeout is by using JavaScript to request a CSRF token on form submission. The form is then updated with the CSRF token and submitted.

  • Another option is to have some JavaScript that lets the user know their session is about to expire. The user can click a button to continue and refresh the session.

  • Finally, the expected CSRF token could be stored in a cookie. This lets the expected CSRF token outlive the session.[.iokays-translated-6d51f78f7a2fd76cb2de235c8fc636b9] 可能会问为什么预期的 CSRF 令牌默认情况下不存储在 cookie 中。这是因为存在已知的漏洞,其中由其他域可以设置标头(例如,指定 cookie)。这与 Ruby on Rails no longer skips a CSRF checks when the header X-Requested-With is present相同。有关如何执行此漏洞利用的详细信息,请参阅 this webappsec.org thread。另一个缺点是通过移除状态(即超时),您将失去在令牌遭到破坏时强制使令牌失效的能力。

One might ask why the expected CSRF token is not stored in a cookie by default. This is because there are known exploits in which headers (for example, to specify the cookies) can be set by another domain. This is the same reason Ruby on Rails no longer skips a CSRF checks when the header X-Requested-With is present. See this webappsec.org thread for details on how to perform the exploit. Another disadvantage is that by removing the state (that is, the timeout), you lose the ability to forcibly invalidate the token if it is compromised.

Multipart (file upload)

保护多部分请求(文件上传)免受 CSRF 攻击会导致 chicken or the egg问题。为了防止 CSRF 攻击发生,必须读取 HTTP 请求的正文以获取实际 CSRF 令牌。然而,读取正文意味着文件已上传,这意味着外部站点可以上传文件。

Protecting multipart requests (file uploads) from CSRF attacks causes a chicken or the egg problem. To prevent a CSRF attack from occurring, the body of the HTTP request must be read to obtain the actual CSRF token. However, reading the body means that the file is uploaded, which means an external site can upload a file.

使用 multipart/form-data 进行 CSRF 保护有两种选择:

There are two options to using CSRF protection with multipart/form-data:

每个选项都有其权衡之处。

Each option has its trade-offs.

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

Before you integrate Spring Security’s CSRF protection with multipart file upload, you should first ensure that you can upload without the CSRF protection. More information about using multipart forms with Spring, see the 1.1.11. Multipart Resolver section of the Spring reference and the MultipartFilter Javadoc.

Place CSRF Token in the Body

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

The first option is to include the actual CSRF token in the body of the request. By placing the CSRF token in the body, the body is read before authorization is performed. This means that anyone can place temporary files on your server. However, only authorized users can submit a file that is processed by your application. In general, this is the recommended approach, because the temporary file upload should have a negligible impact on most servers.

Include CSRF Token in URL

如果允许未经授权的用户上传临时文件不可接受,另一种方法是将预期 CSRF 令牌作为查询参数包含在表单的action属性中。采用此方法的缺点是查询参数可能会泄露。总体而言,将敏感数据放在正文或标头中以确保其不被泄露被认为是最佳做法。您可以在 RFC 2616 Section 15.1.3 Encoding Sensitive Information in URI’s中找到其他信息。

If letting unauthorized users upload temporary files is not acceptable, an alternative is to include the expected CSRF token as a query parameter in the action attribute of the form. The disadvantage to this approach is that query parameters can be leaked. More generally, it is considered best practice to place sensitive data within the body or headers to ensure it is not leaked. You can find additional information in RFC 2616 Section 15.1.3 Encoding Sensitive Information in URI’s.

HiddenHttpMethodFilter

一些应用程序可以使用表单参数来覆盖 HTTP 方法。例如,以下表单可以将 HTTP 方法视为 delete 而不是 post

Some applications can use a form parameter to override the HTTP method. For example, the following form can treat the HTTP method as a delete rather than a post.

CSRF Hidden HTTP Method Form
<form action="/process"
	method="post">
	<!-- ... -->
	<input type="hidden"
		name="_method"
		value="delete"/>
</form>

覆盖 HTTP 方法是在过滤器中发生的。该过滤器必须置于 Spring Security 支持之前。请注意,覆盖仅发生在 post 上,所以这实际上不太可能导致任何实际问题。然而,确保其置于 Spring Security 的过滤器之前仍然是最佳实践。

Overriding the HTTP method occurs in a filter. That filter must be placed before Spring Security’s support. Note that overriding happens only on a post, so this is actually unlikely to cause any real problems. However, it is still best practice to ensure that it is placed before Spring Security’s filters.