mirror of
				https://github.com/spring-projects/spring-security.git
				synced 2025-10-28 13:18:45 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			390 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			390 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.
 | |
| 
 | |
| 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.
 |