395 lines
20 KiB
Plaintext
395 lines
20 KiB
Plaintext
// FIXME: Add links to Servlet and WebFlux support
|
|
|
|
[[csrf]]
|
|
= Cross Site Request Forgery (CSRF)
|
|
|
|
Spring provides comprehensive support for protecting against https://en.wikipedia.org/wiki/Cross-site_request_forgery[Cross Site Request Forgery (CSRF)] attacks.
|
|
In the following sections, we explore:
|
|
|
|
* <<csrf-explained>>
|
|
* <<csrf-protection>>
|
|
* <<csrf-considerations>>
|
|
|
|
// FIXME: Add WebFlux csrf documentation (the link below is broken)
|
|
[NOTE]
|
|
====
|
|
This portion of the documentation discusses the general topic of CSRF protection.
|
|
See the relevant sections for specific information on CSRF protection for xref:servlet/exploits/csrf.adoc#servlet-csrf[servlet] and xref:reactive/exploits/csrf.adoc#webflux-csrf[WebFlux] based applications.
|
|
====
|
|
|
|
[[csrf-explained]]
|
|
== What is a CSRF Attack?
|
|
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
|
|
[source,html]
|
|
----
|
|
<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>
|
|
----
|
|
|
|
The corresponding HTTP request might look like:
|
|
|
|
.Transfer HTTP request
|
|
[source]
|
|
----
|
|
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
|
|
----
|
|
|
|
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
|
|
[source,html]
|
|
----
|
|
<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>
|
|
----
|
|
|
|
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.
|
|
|
|
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 https://www.owasp.org/index.php/Cross-site_Scripting_(XSS)[XSS attack].
|
|
So how do we protect our users from such attacks?
|
|
|
|
[[csrf-protection]]
|
|
== Protecting Against CSRF Attacks
|
|
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 provides two mechanisms to protect against CSRF attacks:
|
|
|
|
* The <<Synchronizer Token Pattern>>
|
|
* Specifying the <<SameSite Attribute>> on your session cookie
|
|
|
|
[NOTE]
|
|
====
|
|
Both protections require that <<csrf-protection-read-only,Safe Methods be Read-only>>.
|
|
====
|
|
|
|
[[csrf-protection-read-only]]
|
|
=== Safe Methods Must be Read-only
|
|
|
|
For <<csrf-protection,either protection>> against CSRF to work, the application must ensure that https://tools.ietf.org/html/rfc7231#section-4.2.1["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.
|
|
|
|
[[csrf-protection-stp]]
|
|
=== Synchronizer Token Pattern
|
|
The predominant and most comprehensive way to protect against CSRF attacks is to use the https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#synchronizer-token-pattern[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.
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
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,safe HTTP methods are 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.
|
|
|
|
Consider how <<csrf-explained,our example>> 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
|
|
[source,html]
|
|
----
|
|
<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>
|
|
----
|
|
|
|
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.
|
|
|
|
The corresponding HTTP request to transfer money would look like this:
|
|
|
|
.Synchronizer Token request
|
|
[source]
|
|
----
|
|
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
|
|
----
|
|
|
|
|
|
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.
|
|
|
|
[[csrf-protection-ssa]]
|
|
=== SameSite Attribute
|
|
An emerging way to protect against <<csrf,CSRF Attacks>> is to specify the https://tools.ietf.org/html/draft-west-first-party-cookies[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.
|
|
|
|
[NOTE]
|
|
====
|
|
Spring Security does not directly control the creation of the session cookie, so it does not provide support for the SameSite attribute.
|
|
https://spring.io/projects/spring-session[Spring Session] provides support for the `SameSite` attribute in servlet-based applications.
|
|
Spring Framework's https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/server/session/CookieWebSessionIdResolver.html[`CookieWebSessionIdResolver`] provides out of the box support for the `SameSite` attribute in WebFlux-based applications.
|
|
====
|
|
|
|
An example, of an HTTP response header with the `SameSite` attribute might look like:
|
|
|
|
.SameSite HTTP response
|
|
[source]
|
|
----
|
|
Set-Cookie: JSESSIONID=randomid; Domain=bank.example.com; Secure; HttpOnly; SameSite=Lax
|
|
----
|
|
|
|
Valid values for the `SameSite` attribute are:
|
|
|
|
* `Strict`: When specified, any request coming from the https://tools.ietf.org/html/draft-west-first-party-cookies-07#section-2.1[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 https://tools.ietf.org/html/draft-west-first-party-cookies-07#section-2.1[same-site] or when the request comes from top-level navigations and the <<Safe Methods Must be Read-only,method is read-only>>.
|
|
Otherwise, the cookie is not included in the HTTP request.
|
|
|
|
Consider how <<csrf-explained,our example>> could be protected using the `SameSite` attribute.
|
|
The bank application can protect against CSRF by specifying the `SameSite` attribute on the session cookie.
|
|
|
|
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.
|
|
|
|
There are some important https://tools.ietf.org/html/draft-west-first-party-cookies-07#section-5[considerations] to be aware of when using `SameSite` attribute to protect against CSRF attacks.
|
|
|
|
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 https://social.example.com.
|
|
The user receives an email at 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.
|
|
|
|
[NOTE]
|
|
====
|
|
We could improve the protection and usability of `SameSite` protection against CSRF attacks by implementing https://github.com/spring-projects/spring-security/issues/7537[gh-7537].
|
|
====
|
|
|
|
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 https://developer.mozilla.org/en-US/docs/Web/HTTP/headers/Set-Cookie#Browser_compatibility[support the SameSite attribute].
|
|
However, older browsers that are still in use may not.
|
|
|
|
For this reason, we generally recommend using the `SameSite` attribute as a defense in depth rather than the sole protection against CSRF attacks.
|
|
|
|
[[csrf-when]]
|
|
== When to use CSRF protection
|
|
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-when-json]]
|
|
=== CSRF protection and JSON
|
|
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 http://blog.opensecurityresearch.com/2012/02/json-csrf-with-parameter-padding.html[CSRF with JSON by using the following form]:
|
|
|
|
.CSRF with JSON form
|
|
[source,html]
|
|
----
|
|
<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>
|
|
----
|
|
|
|
|
|
This produces the following JSON structure
|
|
|
|
.CSRF with JSON request
|
|
[source,javascript]
|
|
----
|
|
{ "amount": 100,
|
|
"routingNumber": "evilsRoutingNumber",
|
|
"account": "evilsAccountNumber",
|
|
"ignore_me": "=test"
|
|
}
|
|
----
|
|
|
|
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
|
|
[source,html]
|
|
----
|
|
<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-when-stateless]]
|
|
=== CSRF and Stateless Browser Applications
|
|
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.
|
|
|
|
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.
|
|
|
|
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 Considerations
|
|
There are a few special considerations to consider when implementing protection against CSRF attacks.
|
|
|
|
// FIXME: Document rotating the CSRF token at log in to avoid a fixation attack
|
|
|
|
[[csrf-considerations-login]]
|
|
=== Logging In
|
|
|
|
To protect against https://en.wikipedia.org/wiki/Cross-site_request_forgery#Forging_login_requests[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:
|
|
|
|
. A malicious user performs a CSRF login with the malicious user's credentials.
|
|
The victim is now authenticated as the malicious user.
|
|
. The malicious user then tricks the victim into visiting the compromised website and entering sensitive information.
|
|
. 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.
|
|
|
|
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-considerations-timeouts>>.
|
|
|
|
[[csrf-considerations-logout]]
|
|
=== Logging Out
|
|
|
|
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 https://labs.detectify.com/2017/03/15/loginlogout-csrf-time-to-reconsider/[this blog post].
|
|
|
|
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-considerations-timeouts>>.
|
|
|
|
[[csrf-considerations-timeouts]]
|
|
=== CSRF and Session Timeouts
|
|
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.
|
|
+
|
|
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 https://weblog.rubyonrails.org/2011/2/8/csrf-protection-bypass-in-ruby-on-rails/[no longer skips a CSRF checks when the header X-Requested-With is present].
|
|
See https://web.archive.org/web/20210221120355/https://lists.webappsec.org/pipermail/websecurity_lists.webappsec.org/2011-February/007533.html[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.
|
|
|
|
// FIXME: Document timeout with lengthy form expire. We do not want to automatically replay that request because it can lead to an exploit.
|
|
|
|
[[csrf-considerations-multipart]]
|
|
=== Multipart (file upload)
|
|
|
|
Protecting multipart requests (file uploads) from CSRF attacks causes a https://en.wikipedia.org/wiki/Chicken_or_the_egg[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.
|
|
|
|
There are two options to using CSRF protection with multipart/form-data:
|
|
|
|
* <<csrf-considerations-multipart-body,Place CSRF Token in the Body>>
|
|
* <<csrf-considerations-multipart-url,Place CSRF Token in the URL>>
|
|
|
|
Each option has its trade-offs.
|
|
|
|
[NOTE]
|
|
====
|
|
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 https://docs.spring.io/spring/docs/5.2.x/spring-framework-reference/web.html#mvc-multipart[1.1.11. Multipart Resolver] section of the Spring reference and the https://docs.spring.io/spring/docs/5.2.x/javadoc-api/org/springframework/web/multipart/support/MultipartFilter.html[`MultipartFilter` Javadoc].
|
|
====
|
|
|
|
[[csrf-considerations-multipart-body]]
|
|
==== Place CSRF Token in the Body
|
|
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.
|
|
|
|
[[csrf-considerations-multipart-url]]
|
|
==== Include CSRF Token in URL
|
|
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 https://www.w3.org/Protocols/rfc2616/rfc2616-sec15.html#sec15.1.3[RFC 2616 Section 15.1.3 Encoding Sensitive Information in URI's].
|
|
|
|
[[csrf-considerations-override-method]]
|
|
==== HiddenHttpMethodFilter
|
|
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
|
|
[source,html]
|
|
----
|
|
<form action="/process"
|
|
method="post">
|
|
<!-- ... -->
|
|
<input type="hidden"
|
|
name="_method"
|
|
value="delete"/>
|
|
</form>
|
|
----
|
|
|
|
|
|
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.
|