|
@@ -1,170 +1,101 @@
|
|
|
-[[csrf]]
|
|
|
-== Cross Site Request Forgery (CSRF)
|
|
|
-This section discusses Spring Security's https://en.wikipedia.org/wiki/Cross-site_request_forgery[ Cross Site Request Forgery (CSRF)] support.
|
|
|
+[[servlet-csrf]]
|
|
|
+= Cross Site Request Forgery (CSRF) for Servlet Environments
|
|
|
|
|
|
+This section discusses Spring Security's <<csrf,Cross Site Request Forgery (CSRF)>> support for servlet environments.
|
|
|
|
|
|
-=== CSRF Attacks
|
|
|
-Before we discuss how Spring Security can protect applications from CSRF attacks, we will explain what a CSRF attack is.
|
|
|
-Let's take a look at a concrete example to get a better understanding.
|
|
|
+[[servelt-csrf-using]]
|
|
|
+== Using Spring Security CSRF Protection
|
|
|
+The steps to using Spring Security's CSRF protection are outlined below:
|
|
|
|
|
|
-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 HTTP request might look like:
|
|
|
-
|
|
|
-
|
|
|
-[source]
|
|
|
-----
|
|
|
-POST /transfer HTTP/1.1
|
|
|
-Host: bank.example.com
|
|
|
-Cookie: JSESSIONID=randomid; Domain=bank.example.com; Secure; HttpOnly
|
|
|
-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:
|
|
|
-
|
|
|
-[source,xml]
|
|
|
-----
|
|
|
-<form action="https://bank.example.com/transfer" method="post">
|
|
|
-<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.
|
|
|
-
|
|
|
-Worst yet, this whole process could have been automated using JavaScript.
|
|
|
-This means you didn't even need to click on the button.
|
|
|
-So how do we protect ourselves from such attacks?
|
|
|
-
|
|
|
-=== Synchronizer Token Pattern
|
|
|
-The issue is that the HTTP request from the bank's website and the request from the evil website are exactly the same.
|
|
|
-This means there is no way to reject requests coming from the evil website and allow 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.
|
|
|
-
|
|
|
-One solution is to use the https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#General_Recommendation:_Synchronizer_Token_Pattern[Synchronizer Token Pattern].
|
|
|
-This solution is to ensure that each request requires, in addition to our session cookie, a randomly generated token as an HTTP parameter.
|
|
|
-When a request is submitted, the server must look up the expected value for the parameter and compare it against the actual value in the request.
|
|
|
-If the values do not match, the request should fail.
|
|
|
-
|
|
|
-We can relax the expectations to only require the token for each HTTP request that updates state.
|
|
|
-This can be safely done since the same origin policy ensures the evil site cannot read the response.
|
|
|
-Additionally, we do not want to include the random token in HTTP GET as this can cause the tokens to be leaked.
|
|
|
-
|
|
|
-Let's take a look at how our example would change.
|
|
|
-Assume the randomly generated token is present in an HTTP parameter named _csrf.
|
|
|
-For example, the request to transfer money would look like this:
|
|
|
-
|
|
|
-
|
|
|
-[source]
|
|
|
-----
|
|
|
-POST /transfer HTTP/1.1
|
|
|
-Host: bank.example.com
|
|
|
-Cookie: JSESSIONID=randomid; Domain=bank.example.com; Secure; HttpOnly
|
|
|
-Content-Type: application/x-www-form-urlencoded
|
|
|
-
|
|
|
-amount=100.00&routingNumber=1234&account=9876&_csrf=<secure-random>
|
|
|
-----
|
|
|
+* <<servlet-csrf-idempotent,Use proper HTTP verbs>>
|
|
|
+* <<servlet-csrf-configure,Configure CSRF Protection>>
|
|
|
+* <<servlet-csrf-include,Include the CSRF Token>>
|
|
|
|
|
|
+[[servlet-csrf-idempotent]]
|
|
|
+=== Use proper HTTP verbs
|
|
|
+The first step to protecting against CSRF attacks is to ensure your website uses proper HTTP verbs.
|
|
|
+This is covered in detail in <<csrf-protection-idempotent,Safe Methods Must be Idempotent>>.
|
|
|
|
|
|
-You will notice that we added the _csrf parameter with a random value.
|
|
|
-Now the evil website will not be able to guess 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 token to the expected token.
|
|
|
+[[servlet-csrf-configure]]
|
|
|
+=== Configure CSRF Protection
|
|
|
+The next step is to configure Spring Security's CSRF protection within your application.
|
|
|
+Spring Security's CSRF protection is enabled by default, but you may need to customize the configuration.
|
|
|
+Below are a few common customizations.
|
|
|
|
|
|
+[[servlet-csrf-configure-custom-repository]]
|
|
|
+==== Custom CsrfTokenRepository
|
|
|
|
|
|
-=== 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 only creating a service that is used by non-browser clients, you will likely want to disable CSRF protection.
|
|
|
+By default Spring Security stores the expected CSRF token in the `HttpSession` using `HttpSessionCsrfTokenRepository`.
|
|
|
+There can be cases where users will want to configure a custom `CsrfTokenRepository`.
|
|
|
+For example, it might be desirable to persist the `CsrfToken` in a cookie to <<servlet-csrf-include-ajax-auto,support a JavaScript based application>>.
|
|
|
|
|
|
+By default the `CookieCsrfTokenRepository` will write to a cookie named `XSRF-TOKEN` and read it from a header named `X-XSRF-TOKEN` or the HTTP parameter `_csrf`.
|
|
|
+These defaults come from https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection[AngularJS]
|
|
|
|
|
|
-==== 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 using the following form]:
|
|
|
+You can configure `CookieCsrfTokenRepository` in XML using the following:
|
|
|
|
|
|
|
|
|
+.Store CSRF Token in a Cookie with XML Configuration
|
|
|
+====
|
|
|
[source,xml]
|
|
|
----
|
|
|
-<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>
|
|
|
+<http>
|
|
|
+ <!-- ... -->
|
|
|
+ <csrf token-repository-ref="tokenRepository"/>
|
|
|
+</http>
|
|
|
+<b:bean id="tokenRepository"
|
|
|
+ class="org.springframework.security.web.csrf.CookieCsrfTokenRepository"
|
|
|
+ p:cookieHttpOnly="false"/>
|
|
|
----
|
|
|
+====
|
|
|
|
|
|
+[NOTE]
|
|
|
+====
|
|
|
+The sample explicitly sets `cookieHttpOnly=false`.
|
|
|
+This is necessary to allow JavaScript (i.e. AngularJS) to read it.
|
|
|
+If you do not need the ability to read the cookie with JavaScript directly, it is recommended to omit `cookieHttpOnly=false` to improve security.
|
|
|
+====
|
|
|
|
|
|
-This will produce the following JSON structure
|
|
|
|
|
|
+You can configure `CookieCsrfTokenRepository` in Java Configuration using:
|
|
|
|
|
|
-[source,javascript]
|
|
|
-----
|
|
|
-{ "amount": 100,
|
|
|
-"routingNumber": "evilsRoutingNumber",
|
|
|
-"account": "evilsAccountNumber",
|
|
|
-"ignore_me": "=test"
|
|
|
-}
|
|
|
+.Store CSRF Token in a Cookie with Java Configuration
|
|
|
+====
|
|
|
+[source,java]
|
|
|
----
|
|
|
+@EnableWebSecurity
|
|
|
+public class WebSecurityConfig extends
|
|
|
+ WebSecurityConfigurerAdapter {
|
|
|
|
|
|
-If an application were not validating the Content-Type, then 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 shown below:
|
|
|
-
|
|
|
-[source,xml]
|
|
|
-----
|
|
|
-<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>
|
|
|
+ @Override
|
|
|
+ protected void configure(HttpSecurity http) {
|
|
|
+ http
|
|
|
+ .csrf(csrf ->
|
|
|
+ csrf
|
|
|
+ .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|
|
|
----
|
|
|
+====
|
|
|
|
|
|
-==== CSRF and Stateless Browser Applications
|
|
|
-What if my application is stateless? That doesn't 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 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 will be sent with the request in the same manner that the JSESSIONID cookie was sent in our previous example.
|
|
|
-
|
|
|
-Users using basic authentication are also vulnerable to CSRF attacks since the browser will automatically include the username password in any requests in the same manner that the JSESSIONID cookie was sent in our previous example.
|
|
|
-
|
|
|
-[[csrf-using]]
|
|
|
-=== Using Spring Security CSRF Protection
|
|
|
-So what are the steps necessary to use Spring Security's to protect our site against CSRF attacks? The steps to using Spring Security's CSRF protection are outlined below:
|
|
|
-
|
|
|
-* <<csrf-use-proper-verbs,Use proper HTTP verbs>>
|
|
|
-* <<csrf-configure,Configure CSRF Protection>>
|
|
|
-* <<csrf-include-csrf-token,Include the CSRF Token>>
|
|
|
-
|
|
|
-[[csrf-use-proper-verbs]]
|
|
|
-==== Use proper HTTP verbs
|
|
|
-The first step to protecting against CSRF attacks is to ensure your website uses proper HTTP verbs.
|
|
|
-Specifically, before Spring Security's CSRF support can be of use, you need to be certain that your application is using PATCH, POST, PUT, and/or DELETE for anything that modifies state.
|
|
|
-
|
|
|
-This is not a limitation of Spring Security's support, but instead a general requirement for proper CSRF prevention.
|
|
|
-The reason is that including private information in an HTTP GET can cause the information to be leaked.
|
|
|
-See https://www.w3.org/Protocols/rfc2616/rfc2616-sec15.html#sec15.1.3[RFC 2616 Section 15.1.3 Encoding Sensitive Information in URI's] for general guidance on using POST instead of GET for sensitive information.
|
|
|
+[NOTE]
|
|
|
+====
|
|
|
+The sample explicitly sets `cookieHttpOnly=false`.
|
|
|
+This is necessary to allow JavaScript (i.e. AngularJS) to read it.
|
|
|
+If you do not need the ability to read the cookie with JavaScript directly, it is recommended to omit `cookieHttpOnly=false` (by using `new CookieCsrfTokenRepository()` instead) to improve security.
|
|
|
+====
|
|
|
|
|
|
+[[servlet-csrf-configure-disable]]
|
|
|
+==== Disable CSRF Protection
|
|
|
+CSRF protection is enabled by default.
|
|
|
+However, it is simple to disable CSRF protection if it <<csrf-when,makes sense for your application>>.
|
|
|
|
|
|
-[[csrf-configure]]
|
|
|
-==== Configure CSRF Protection
|
|
|
-The next step is to include Spring Security's CSRF protection within your application.
|
|
|
-Some frameworks handle invalid CSRF tokens by invaliding the user's session, but this causes <<csrf-logout,its own problems>>.
|
|
|
-Instead by default Spring Security's CSRF protection will produce an HTTP 403 access denied.
|
|
|
-This can be customized by configuring the <<access-denied-handler,AccessDeniedHandler>> to process `InvalidCsrfTokenException` differently.
|
|
|
+The XML configuration below will disable CSRF protection.
|
|
|
|
|
|
-As of Spring Security 4.0, CSRF protection is enabled by default with XML configuration.
|
|
|
-If you would like to disable CSRF protection, the corresponding XML configuration can be seen below.
|
|
|
|
|
|
+.Disable CSRF XML Configuration
|
|
|
+====
|
|
|
[source,xml]
|
|
|
----
|
|
|
<http>
|
|
@@ -172,36 +103,78 @@ If you would like to disable CSRF protection, the corresponding XML configuratio
|
|
|
<csrf disabled="true"/>
|
|
|
</http>
|
|
|
----
|
|
|
+====
|
|
|
|
|
|
-CSRF protection is enabled by default with Java Configuration.
|
|
|
-If you would like to disable CSRF, the corresponding Java configuration can be seen below.
|
|
|
-Refer to the Javadoc of csrf() for additional customizations in how CSRF protection is configured.
|
|
|
+The Java configuration below will disable CSRF protection.
|
|
|
|
|
|
+.Disable CSRF Java Configuration
|
|
|
+====
|
|
|
[source,java]
|
|
|
----
|
|
|
+@Configuration
|
|
|
@EnableWebSecurity
|
|
|
public class WebSecurityConfig extends
|
|
|
-WebSecurityConfigurerAdapter {
|
|
|
+ WebSecurityConfigurerAdapter {
|
|
|
|
|
|
@Override
|
|
|
- protected void configure(HttpSecurity http) throws Exception {
|
|
|
+ protected void configure(HttpSecurity http) {
|
|
|
http
|
|
|
.csrf(csrf ->
|
|
|
- csrf.disable()
|
|
|
+ csrf.disable()
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
----
|
|
|
+====
|
|
|
+
|
|
|
+[[servlet-csrf-include]]
|
|
|
+=== Include the CSRF Token
|
|
|
+
|
|
|
+In order for the <<csrf-protection-stp,synchronizer token pattern>> to protect against CSRF attacks, we must include the actual CSRF token in the HTTP request.
|
|
|
+This must be included in a part of the request (i.e. form parameter, HTTP header, etc) that is not automatically included in the HTTP request by the browser.
|
|
|
+
|
|
|
+Spring Security's https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/csrf/CsrfFilter.html[CsrfFilter] exposes a https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/csrf/CsrfToken.html[CsrfToken] as an `HttpServletRequest` attribute named `_csrf`.
|
|
|
+This means that any view technology can access the `CsrfToken` to expose the expected token as either a <<servlet-csrf-include-form-attr,form>> or <<servlet-csrf-include-ajax-meta-attr,meta tag>>.
|
|
|
+Fortunately, there are integrations listed below that make including the token in <<servlet-csrf-include-form,form>> and <<servlet-csrf-include-ajax,ajax>> requests even easier.
|
|
|
+
|
|
|
+[[servlet-csrf-include-form]]
|
|
|
+==== Form URL Encoded
|
|
|
+In order to post an HTML form the CSRF token must be included in the form as a hidden input.
|
|
|
+For example, the rendered HTML might look like:
|
|
|
+
|
|
|
+.CSRF Token HTML
|
|
|
+====
|
|
|
+[source,html]
|
|
|
+----
|
|
|
+<input type="hidden"
|
|
|
+ name="_csrf"
|
|
|
+ value="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
|
|
|
+----
|
|
|
+====
|
|
|
+
|
|
|
+Next we will discuss various ways of including the CSRF token in a form as a hidden input.
|
|
|
+
|
|
|
+[[servlet-csrf-include-form-auto]]
|
|
|
+===== Automatic CSRF Token Inclusion
|
|
|
+
|
|
|
+Spring Security's CSRF support provides integration with Spring's https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/support/RequestDataValueProcessor.html[RequestDataValueProcessor] via its https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/servlet/support/csrf/CsrfRequestDataValueProcessor.html[CsrfRequestDataValueProcessor].
|
|
|
+This means that if you leverage https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-view-jsp-formtaglib[Spring’s form tag library], https://www.thymeleaf.org/doc/tutorials/2.1/thymeleafspring.html#integration-with-requestdatavalueprocessor[Thymleaf], or any other view technology that integrates with `RequestDataValueProcessor`, then forms that have an unsafe HTTP method (i.e. post) will automatically include the actual CSRF token.
|
|
|
+
|
|
|
+[[servlet-csrf-include-form-tag]]
|
|
|
+===== csrfInput Tag
|
|
|
+
|
|
|
+If you are using JSPs, then you can use https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-view-jsp-formtaglib[Spring’s form tag library].
|
|
|
+However, if that is not an option, you can also easily include the token with the <<taglibs-csrfinput,csrfInput>> tag.
|
|
|
|
|
|
-[[csrf-include-csrf-token]]
|
|
|
-==== Include the CSRF Token
|
|
|
+[[servlet-csrf-include-form-attr]]
|
|
|
+===== CsrfToken Request Attribute
|
|
|
+
|
|
|
+If the <<servlet-csrf-include,other options>> for including the actual CSRF token in the request do not work, you can take advantage of the fact that the `CsrfToken` <<servlet-csrf-include,is exposed>> as an `HttpServletRequest` attribute named `_csrf`.
|
|
|
|
|
|
-[[csrf-include-csrf-token-form]]
|
|
|
-===== Form Submissions
|
|
|
-The last step is to ensure that you include the CSRF token in all PATCH, POST, PUT, and DELETE methods.
|
|
|
-One way to approach this is to use the `_csrf` request attribute to obtain the current `CsrfToken`.
|
|
|
An example of doing this with a JSP is shown below:
|
|
|
|
|
|
+.CSRF Token in Form with Request Attribute
|
|
|
+====
|
|
|
[source,xml]
|
|
|
----
|
|
|
<c:url var="logoutUrl" value="/logout"/>
|
|
@@ -214,217 +187,177 @@ An example of doing this with a JSP is shown below:
|
|
|
value="${_csrf.token}"/>
|
|
|
</form>
|
|
|
----
|
|
|
-
|
|
|
-An easier approach is to use <<the-csrfinput-tag,the csrfInput tag>> from the Spring Security JSP tag library.
|
|
|
-
|
|
|
-[NOTE]
|
|
|
-====
|
|
|
-If you are using Spring MVC `<form:form>` tag or https://www.thymeleaf.org/whatsnew21.html#reqdata[Thymeleaf 2.1+] and are using `@EnableWebSecurity`, the `CsrfToken` is automatically included for you (using the `CsrfRequestDataValueProcessor`).
|
|
|
====
|
|
|
|
|
|
-[[csrf-include-csrf-token-ajax]]
|
|
|
-===== Ajax and JSON Requests
|
|
|
+[[servlet-csrf-include-ajax]]
|
|
|
+==== Ajax and JSON Requests
|
|
|
If you are using JSON, then it is not possible to submit the CSRF token within an HTTP parameter.
|
|
|
Instead you can submit the token within a HTTP header.
|
|
|
-A typical pattern would be to include the CSRF token within your meta tags.
|
|
|
-An example with a JSP is shown below:
|
|
|
|
|
|
+In the following sections we will discuss various ways of including the CSRF token as an HTTP request header in JavaScript based applications.
|
|
|
|
|
|
-[source,xml]
|
|
|
+[[servlet-csrf-include-ajax-auto]]
|
|
|
+===== Automatic Inclusion
|
|
|
+
|
|
|
+Spring Security can easily be <<servlet-csrf-configure-custom-repository,configured>> to store the expected CSRF token in a cookie.
|
|
|
+By storing the expected CSRF in a cookie, JavaScript frameworks like https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection[AngularJS] will automatically include the actual CSRF token in the HTTP request headers.
|
|
|
+
|
|
|
+[[servlet-csrf-include-ajax-meta]]
|
|
|
+===== Meta tags
|
|
|
+
|
|
|
+An alternative pattern to <<servlet-csrf-include-form-attr-auto,exposing the CSRF in a cookie>> is to include the CSRF token within your `meta` tags.
|
|
|
+The HTML might look something like this:
|
|
|
+
|
|
|
+.CSRF meta tag HTML
|
|
|
+====
|
|
|
+[source,html]
|
|
|
----
|
|
|
<html>
|
|
|
<head>
|
|
|
- <meta name="_csrf" content="${_csrf.token}"/>
|
|
|
- <!-- default header name is X-CSRF-TOKEN -->
|
|
|
- <meta name="_csrf_header" content="${_csrf.headerName}"/>
|
|
|
+ <meta name="_csrf" content="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
|
|
|
+ <meta name="_csrf_header" content="X-CSRF-TOKEN"/>
|
|
|
<!-- ... -->
|
|
|
</head>
|
|
|
<!-- ... -->
|
|
|
----
|
|
|
+====
|
|
|
|
|
|
-Instead of manually creating the meta tags, you can use the simpler <<the-csrfmetatags-tag,csrfMetaTags tag>> from the Spring Security JSP tag library.
|
|
|
-
|
|
|
-You can then include the token within all your Ajax requests.
|
|
|
+Once the meta tags contained the CSRF token, the JavaScript code would read the meta tags and include the CSRF token as a header.
|
|
|
If you were using jQuery, this could be done with the following:
|
|
|
|
|
|
+.AJAX send CSRF Token
|
|
|
+====
|
|
|
[source,javascript]
|
|
|
----
|
|
|
$(function () {
|
|
|
-var token = $("meta[name='_csrf']").attr("content");
|
|
|
-var header = $("meta[name='_csrf_header']").attr("content");
|
|
|
-$(document).ajaxSend(function(e, xhr, options) {
|
|
|
- xhr.setRequestHeader(header, token);
|
|
|
-});
|
|
|
+ var token = $("meta[name='_csrf']").attr("content");
|
|
|
+ var header = $("meta[name='_csrf_header']").attr("content");
|
|
|
+ $(document).ajaxSend(function(e, xhr, options) {
|
|
|
+ xhr.setRequestHeader(header, token);
|
|
|
+ });
|
|
|
});
|
|
|
----
|
|
|
+====
|
|
|
|
|
|
-As an alternative to jQuery, we recommend using https://github.com/cujojs[cujoJS's] rest.js.
|
|
|
-The https://github.com/cujojs/rest[rest.js] module provides advanced support for working with HTTP requests and responses in RESTful ways.
|
|
|
-A core capability is the ability to contextualize the HTTP client adding behavior as needed by chaining interceptors on to the client.
|
|
|
-
|
|
|
-[source,javascript]
|
|
|
-----
|
|
|
-var client = rest.chain(csrf, {
|
|
|
-token: $("meta[name='_csrf']").attr("content"),
|
|
|
-name: $("meta[name='_csrf_header']").attr("content")
|
|
|
-});
|
|
|
-----
|
|
|
-
|
|
|
-
|
|
|
-The configured client can be shared with any component of the application that needs to make a request to the CSRF protected resource.
|
|
|
-One significant difference between rest.js and jQuery is that only requests made with the configured client will contain the CSRF token, vs jQuery where __all__ requests will include the token.
|
|
|
-The ability to scope which requests receive the token helps guard against leaking the CSRF token to a third party.
|
|
|
-Please refer to the https://github.com/cujojs/rest/tree/master/docs[rest.js reference documentation] for more information on rest.js.
|
|
|
-
|
|
|
-[[csrf-cookie]]
|
|
|
-===== CookieCsrfTokenRepository
|
|
|
+[[servlet-csrf-include-ajax-meta-tag]]
|
|
|
+====== csrfMeta tag
|
|
|
|
|
|
-There can be cases where users will want to persist the `CsrfToken` in a cookie.
|
|
|
-By default the `CookieCsrfTokenRepository` will write to a cookie named `XSRF-TOKEN` and read it from a header named `X-XSRF-TOKEN` or the HTTP parameter `_csrf`.
|
|
|
-These defaults come from https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection[AngularJS]
|
|
|
+If you are using JSPs a simple way to write the CSRF token to the `meta` tags is by leveraging the <<taglibs-csrfmeta,csrfMeta>> tag.
|
|
|
|
|
|
-You can configure `CookieCsrfTokenRepository` in XML using the following:
|
|
|
+[[servlet-csrf-include-ajax-meta-attr]]
|
|
|
+====== CsrfToken Request Attribute
|
|
|
|
|
|
-[source,xml]
|
|
|
-----
|
|
|
-<http>
|
|
|
- <!-- ... -->
|
|
|
- <csrf token-repository-ref="tokenRepository"/>
|
|
|
-</http>
|
|
|
-<b:bean id="tokenRepository"
|
|
|
- class="org.springframework.security.web.csrf.CookieCsrfTokenRepository"
|
|
|
- p:cookieHttpOnly="false"/>
|
|
|
-----
|
|
|
+If the <<servlet-csrf-include,other options>> for including the actual CSRF token in the request do not work, you can take advantage of the fact that the `CsrfToken` <<servlet-csrf-include,is exposed>> as an `HttpServletRequest` attribute named `_csrf`.
|
|
|
+An example of doing this with a JSP is shown below:
|
|
|
|
|
|
-[NOTE]
|
|
|
-====
|
|
|
-The sample explicitly sets `cookieHttpOnly=false`.
|
|
|
-This is necessary to allow JavaScript (i.e. AngularJS) to read it.
|
|
|
-If you do not need the ability to read the cookie with JavaScript directly, it is recommended to omit `cookieHttpOnly=false` to improve security.
|
|
|
+.CSRF meta tag JSP
|
|
|
====
|
|
|
-
|
|
|
-
|
|
|
-You can configure `CookieCsrfTokenRepository` in Java Configuration using:
|
|
|
-
|
|
|
-[source,java]
|
|
|
+[source,html]
|
|
|
----
|
|
|
-@EnableWebSecurity
|
|
|
-public class WebSecurityConfig extends
|
|
|
- WebSecurityConfigurerAdapter {
|
|
|
-
|
|
|
- @Override
|
|
|
- protected void configure(HttpSecurity http) throws Exception {
|
|
|
- http
|
|
|
- .csrf(csrf ->
|
|
|
- csrf
|
|
|
- .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
|
|
|
- );
|
|
|
- }
|
|
|
-}
|
|
|
+<html>
|
|
|
+<head>
|
|
|
+ <meta name="_csrf" content="${_csrf.token}"/>
|
|
|
+ <!-- default header name is X-CSRF-TOKEN -->
|
|
|
+ <meta name="_csrf_header" content="${_csrf.headerName}"/>
|
|
|
+ <!-- ... -->
|
|
|
+</head>
|
|
|
+<!-- ... -->
|
|
|
----
|
|
|
-
|
|
|
-[NOTE]
|
|
|
====
|
|
|
-The sample explicitly sets `cookieHttpOnly=false`.
|
|
|
-This is necessary to allow JavaScript (i.e. AngularJS) to read it.
|
|
|
-If you do not need the ability to read the cookie with JavaScript directly, it is recommended to omit `cookieHttpOnly=false` (by using `new CookieCsrfTokenRepository()` instead) to improve security.
|
|
|
-====
|
|
|
-
|
|
|
-
|
|
|
-[[csrf-caveats]]
|
|
|
-=== CSRF Caveats
|
|
|
-There are a few caveats when implementing CSRF.
|
|
|
-
|
|
|
-
|
|
|
-[[csrf-timeouts]]
|
|
|
-==== Timeouts
|
|
|
-One issue is that the expected CSRF token is stored in the HttpSession, so as soon as the HttpSession expires your configured `AccessDeniedHandler` will receive a InvalidCsrfTokenException.
|
|
|
-If you are using the default `AccessDeniedHandler`, the browser will get an HTTP 403 and display a poor error message.
|
|
|
|
|
|
-[NOTE]
|
|
|
-====
|
|
|
-One might ask why the expected `CsrfToken` isn't stored in a cookie by default.
|
|
|
-This is because there are known exploits in which headers (i.e. 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 CSRF checks when the header X-Requested-With is present].
|
|
|
-See http://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 (i.e. the timeout) you lose the ability to forcibly terminate the token if it is compromised.
|
|
|
-====
|
|
|
-
|
|
|
-A simple way to mitigate an active user experiencing a timeout 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.
|
|
|
-
|
|
|
-Alternatively, specifying a custom `AccessDeniedHandler` allows you to process the `InvalidCsrfTokenException` any way you like.
|
|
|
-For an example of how to customize the `AccessDeniedHandler` refer to the provided links for both <<nsa-access-denied-handler,xml>> and https://github.com/spring-projects/spring-security/blob/3.2.0.RC1/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/NamespaceHttpAccessDeniedHandlerTests.groovy#L64[Java configuration].
|
|
|
+[[servlet-csrf-considerations]]
|
|
|
+== CSRF Considerations
|
|
|
+There are a few special considerations to consider when implementing protection against CSRF attacks.
|
|
|
+This section discusses those considerations as it pertains to servlet environments.
|
|
|
+Refer to <<csrf-considerations>> for a more general discussion.
|
|
|
|
|
|
-Finally, the application can be configured to use <<csrf-cookie,CookieCsrfTokenRepository>> which will not expire.
|
|
|
-As previously mentioned, this is not as secure as using a session, but in many cases can be good enough.
|
|
|
|
|
|
+[[servlet-considerations-csrf-login]]
|
|
|
+=== Logging In
|
|
|
|
|
|
-[[csrf-login]]
|
|
|
-==== Logging In
|
|
|
-In order to protect against https://en.wikipedia.org/wiki/Cross-site_request_forgery#Forging_login_requests[forging log in requests] the log in form should be protected against CSRF attacks too.
|
|
|
-Since the `CsrfToken` is stored in HttpSession, this means an HttpSession will be created as soon as `CsrfToken` token attribute is accessed.
|
|
|
-While this sounds bad in a RESTful / stateless architecture the reality is that state is necessary to implement practical security.
|
|
|
-Without state, we have nothing we can do if a token is compromised.
|
|
|
-Practically speaking, the CSRF token is quite small in size and should have a negligible impact on our architecture.
|
|
|
+It is important to <<csrf-considerations-login,require CSRF for log in>> requests to protect against forging log in attempts.
|
|
|
+Spring Security's servlet support does this out of the box.
|
|
|
|
|
|
-A common technique to protect the log in form is by using a JavaScript function to obtain a valid CSRF token before the form submission.
|
|
|
-By doing this, there is no need to think about session timeouts (discussed in the previous section) because the session is created right before the form submission (assuming that <<csrf-cookie,CookieCsrfTokenRepository>> isn't configured instead), so the user can stay on the login page and submit the username/password when he wants.
|
|
|
-In order to achieve this, you can take advantage of the `CsrfTokenArgumentResolver` provided by Spring Security and expose an endpoint like it's described on <<mvc-csrf-resolver,here>>.
|
|
|
+[[servlet-considerations-csrf-login]]
|
|
|
+=== Logging Out
|
|
|
|
|
|
-
|
|
|
-[[csrf-logout]]
|
|
|
-==== Logging Out
|
|
|
-Adding CSRF will update the LogoutFilter to only use HTTP POST.
|
|
|
+It is important to <<csrf-considerations-logout,require CSRF for log out>> requests to protect against forging log out attempts.
|
|
|
+If CSRF protection is enabled (default), Spring Security's `LogoutFilter` to only process HTTP POST.
|
|
|
This ensures that log out requires a CSRF token and that a malicious user cannot forcibly log out your users.
|
|
|
|
|
|
-One approach is to use a form for log out.
|
|
|
+The easiest approach is to use a form to log out.
|
|
|
If you really want a link, you can use JavaScript to have the link perform a POST (i.e. maybe on a hidden form).
|
|
|
For browsers with JavaScript that is disabled, you can optionally have the link take the user to a log out confirmation page that will perform the POST.
|
|
|
|
|
|
If you really want to use HTTP GET with logout you can do so, but remember this is generally not recommended.
|
|
|
-For example, the following Java Configuration will perform logout with the URL /logout is requested with any HTTP method:
|
|
|
+For example, the following Java Configuration will perform logout with the URL `/logout` is requested with any HTTP method:
|
|
|
|
|
|
+.Log out with HTTP GET
|
|
|
+====
|
|
|
[source,java]
|
|
|
----
|
|
|
@EnableWebSecurity
|
|
|
public class WebSecurityConfig extends
|
|
|
-WebSecurityConfigurerAdapter {
|
|
|
+ WebSecurityConfigurerAdapter {
|
|
|
|
|
|
@Override
|
|
|
- protected void configure(HttpSecurity http) throws Exception {
|
|
|
+ protected void configure(HttpSecurity http) {
|
|
|
http
|
|
|
.logout(logout ->
|
|
|
- logout
|
|
|
- .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
|
|
|
+ logout
|
|
|
+ .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
----
|
|
|
+====
|
|
|
+
|
|
|
+
|
|
|
+[[servlet-considerations-csrf-timeouts]]
|
|
|
+=== CSRF and Session Timeouts
|
|
|
+
|
|
|
+By default Spring Security stores the CSRF token in the `HttpSession`.
|
|
|
+This can lead to a situation where the session expires which means there is not an expected CSRF token to validate against.
|
|
|
|
|
|
-[[csrf-multipart]]
|
|
|
-==== Multipart (file upload)
|
|
|
-There are two options to using CSRF protection with multipart/form-data.
|
|
|
-Each option has its tradeoffs.
|
|
|
+We've already discussed <<csrf-considerations-login,general solutions>> to session timeouts.
|
|
|
+This section discusses the specifics of CSRF timeouts as it pertains to the servlet support.
|
|
|
|
|
|
-* <<csrf-multipartfilter,Placing MultipartFilter before Spring Security>>
|
|
|
-* <<csrf-include-csrf-token-in-action,Include CSRF token in action>>
|
|
|
+It is simple to change storage of the expected CSRF token to be in a cookie.
|
|
|
+For details, refer to the <<servlet-csrf-configure-custom-repository>> section.
|
|
|
+
|
|
|
+If a token does expire, you might want to customize how it is handled by specifying a custom `AccessDeniedHandler`.
|
|
|
+The custom `AccessDeniedHandler` can process the `InvalidCsrfTokenException` any way you like.
|
|
|
+For an example of how to customize the `AccessDeniedHandler` refer to the provided links for both <<nsa-access-denied-handler,xml>> and https://github.com/spring-projects/spring-security/blob/3.2.0.RC1/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/NamespaceHttpAccessDeniedHandlerTests.groovy#L64[Java configuration].
|
|
|
+// FIXME: We should add a custom AccessDeniedHandler section in the reference and update the links above
|
|
|
+
|
|
|
+
|
|
|
+[[servlet-csrf-considerations-multipart]]
|
|
|
+=== Multipart (file upload)
|
|
|
+We have <<csrf-considerations-multipart,already discussed>> how protecting multipart requests (file uploads) from CSRF attacks causes a https://en.wikipedia.org/wiki/Chicken_or_the_egg[chicken and the egg] problem.
|
|
|
+This section discusses how to implement placing the CSRF token in the <<servlet-csrf-considerations-multipart-body,body>> and <<servlet-csrf-considerations-multipart-url,url>> within a servlet application.
|
|
|
|
|
|
[NOTE]
|
|
|
====
|
|
|
-Before you integrate Spring Security's CSRF protection with multipart file upload, ensure that you can upload without the CSRF protection first.
|
|
|
-More information about using multipart forms with Spring can be found within the https://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/mvc.html#mvc-multipart[17.10 Spring's multipart (file upload) support] section of the Spring reference and the https://docs.spring.io/spring/docs/3.2.x/javadoc-api/org/springframework/web/multipart/support/MultipartFilter.html[MultipartFilter javadoc].
|
|
|
+More information about using multipart forms with Spring can be found within 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-multipartfilter]]
|
|
|
-===== Placing MultipartFilter before Spring Security
|
|
|
-The first option is to ensure that the `MultipartFilter` is specified before the Spring Security filter.
|
|
|
+[[servlet-csrf-considerations-multipart-body]]
|
|
|
+==== Place CSRF Token in the Body
|
|
|
+
|
|
|
+We have <<csrf-considerations-multipart-body,already discussed>> the tradeoffs of placing the CSRF token in the body.
|
|
|
+In this section we will discuss how to configure Spring Security to read the CSRF from the body.
|
|
|
+
|
|
|
+In order to read the CSRF token from the body, the `MultipartFilter` is specified before the Spring Security filter.
|
|
|
Specifying the `MultipartFilter` before the Spring Security filter means that there is no authorization for invoking the `MultipartFilter` which means anyone can place temporary files on your server.
|
|
|
However, only authorized users will be able to submit a File that is processed by your application.
|
|
|
-In general, this is the recommended approach because the temporary file upload should have a negligble impact on most servers.
|
|
|
+In general, this is the recommended approach because the temporary file upload should have a negligible impact on most servers.
|
|
|
+
|
|
|
+// FIXME: Document Spring Boot
|
|
|
|
|
|
To ensure `MultipartFilter` is specified before the Spring Security filter with java configuration, users can override beforeSpringSecurityFilterChain as shown below:
|
|
|
|
|
|
+.Initializer MultipartFilter
|
|
|
+====
|
|
|
[source,java]
|
|
|
----
|
|
|
public class SecurityApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
|
|
@@ -435,9 +368,12 @@ public class SecurityApplicationInitializer extends AbstractSecurityWebApplicati
|
|
|
}
|
|
|
}
|
|
|
----
|
|
|
+====
|
|
|
|
|
|
To ensure `MultipartFilter` is specified before the Spring Security filter with XML configuration, users can ensure the <filter-mapping> element of the `MultipartFilter` is placed before the springSecurityFilterChain within the web.xml as shown below:
|
|
|
|
|
|
+.web.xml - MultipartFilter
|
|
|
+====
|
|
|
[source,xml]
|
|
|
----
|
|
|
<filter>
|
|
@@ -457,34 +393,28 @@ To ensure `MultipartFilter` is specified before the Spring Security filter with
|
|
|
<url-pattern>/*</url-pattern>
|
|
|
</filter-mapping>
|
|
|
----
|
|
|
+====
|
|
|
+
|
|
|
+[[servlet-csrf-considerations-multipart-url]]
|
|
|
+==== Include CSRF Token in URL
|
|
|
|
|
|
-[[csrf-include-csrf-token-in-action]]
|
|
|
-===== Include CSRF token in action
|
|
|
-If allowing unauthorized users to upload temporariy files is not acceptable, an alternative is to place the `MultipartFilter` after the Spring Security filter and include the CSRF as a query parameter in the action attribute of the form.
|
|
|
+If allowing unauthorized users to upload temporary files is not acceptable, an alternative is to place the `MultipartFilter` after the Spring Security filter and include the CSRF as a query parameter in the action attribute of the form.
|
|
|
+Since the `CsrfToken` is exposed as an `HttpServletRequest` <<servlet-csrf-include,request attribute>>, we can use that to create an `action` with the CSRF token in it.
|
|
|
An example with a jsp is shown below
|
|
|
|
|
|
-[source,xml]
|
|
|
+.CSRF Token in Action
|
|
|
+====
|
|
|
+[source,html]
|
|
|
----
|
|
|
-<form action="./upload?${_csrf.parameterName}=${_csrf.token}" method="post" enctype="multipart/form-data">
|
|
|
+<form method="post"
|
|
|
+ action="./upload?${_csrf.parameterName}=${_csrf.token}"
|
|
|
+ enctype="multipart/form-data">
|
|
|
----
|
|
|
+====
|
|
|
|
|
|
-The disadvantage to this approach is that query parameters can be leaked.
|
|
|
-More genearlly, it is considered best practice to place sensitive data within the body or headers to ensure it is not leaked.
|
|
|
-Additional information can be found 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].
|
|
|
-
|
|
|
-==== HiddenHttpMethodFilter
|
|
|
-The HiddenHttpMethodFilter should be placed before the Spring Security filter.
|
|
|
-In general this is true, but it could have additional implications when protecting against CSRF attacks.
|
|
|
-
|
|
|
-Note that the HiddenHttpMethodFilter only overrides the HTTP method on a POST, so this is actually unlikely to cause any real problems.
|
|
|
-However, it is still best practice to ensure it is placed before Spring Security's filters.
|
|
|
-
|
|
|
-=== Overriding Defaults
|
|
|
-Spring Security's goal is to provide defaults that protect your users from exploits.
|
|
|
-This does not mean that you are forced to accept all of its defaults.
|
|
|
-
|
|
|
-For example, you can provide a custom CsrfTokenRepository to override the way in which the `CsrfToken` is stored.
|
|
|
+[[servlet-csrf-considerations-override-method]]
|
|
|
+=== HiddenHttpMethodFilter
|
|
|
+We have <<csrf-considerations-multipart-body,already discussed>> the trade-offs of placing the CSRF token in the body.
|
|
|
|
|
|
-You can also specify a custom RequestMatcher to determine which requests are protected by CSRF (i.e. perhaps you don't care if log out is exploited).
|
|
|
-In short, if Spring Security's CSRF protection doesn't behave exactly as you want it, you are able to customize the behavior.
|
|
|
-Refer to the <<nsa-csrf>> documentation for details on how to make these customizations with XML and the `CsrfConfigurer` javadoc for details on how to make these customizations when using Java configuration.
|
|
|
+In Spring's Servlet support, overriding the HTTP method is done using https://docs.spring.io/spring-framework/docs/5.2.x/javadoc-api/org/springframework/web/filter/reactive/HiddenHttpMethodFilter.html[HiddenHttpMethodFilter].
|
|
|
+More information can be found in https://docs.spring.io/spring/docs/5.2.x/spring-framework-reference/web.html#mvc-rest-method-conversion[HTTP Method Conversion] section of the reference documentation.
|