You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
389 lines
20 KiB
389 lines
20 KiB
// 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. |
|
|
|
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.
|
|
|