This version is still in development and is not considered stable yet. For the latest stable version, please use Spring Security 6.4.1! |
Cross Site Request Forgery (CSRF)
Spring provides comprehensive support for protecting against Cross Site Request Forgery (CSRF) attacks. In the following sections, we explore:
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:
<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:
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:
<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 XSS attack. So how do we protect our users from such attacks?
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:
-
Specifying the SameSite Attribute on your session cookie
Both protections require that Safe Methods be Read-only. |
Safe Methods Must be Read-only
For either 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
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.
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 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 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:
<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:
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.
SameSite Attribute
An emerging way to protect against CSRF Attacks 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 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 |
An example, of an HTTP response header with the SameSite
attribute might look like:
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 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 method is read-only. Otherwise, the cookie is not included in the HTTP request.
Consider how 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 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 social.example.com.
The user receives an email at 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 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.
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 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 CSRF with JSON by using the following 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>
This produces the following JSON structure
{ "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:
<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
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
There are a few special considerations to consider when implementing protection against CSRF attacks.
Logging In
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:
-
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 and Session Timeouts.
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 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 and Session 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 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)
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.
There are two options to using CSRF protection with multipart/form-data:
Each option has its trade-offs.
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 |
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.
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 RFC 2616 Section 15.1.3 Encoding Sensitive Information in URI’s.
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
.
<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.