mirror of
https://github.com/spring-projects/spring-security.git
synced 2026-01-24 23:37:04 +00:00
332 lines
25 KiB
XML
332 lines
25 KiB
XML
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="csrf"
|
||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||
<info>
|
||
<title>Cross Site Request Forgery (CSRF)</title>
|
||
</info>
|
||
<para>This section discusses Spring Security's <link xlink:href="http://en.wikipedia.org/wiki/Cross-site_request_forgery">
|
||
Cross Site Request Forgery (CSRF)</link> support.</para>
|
||
<section>
|
||
<title>CSRF Attacks</title>
|
||
<para>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.</para>
|
||
<para>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:</para>
|
||
<programlisting><![CDATA[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
|
||
]]></programlisting>
|
||
<para>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:</para>
|
||
<programlisting language="xml"><![CDATA[<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>]]></programlisting>
|
||
<para>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.</para>
|
||
<para>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?</para>
|
||
</section>
|
||
<section>
|
||
<title>Synchronizer Token Pattern</title>
|
||
<para>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.</para>
|
||
<para>One solution is to use the
|
||
<link xlink:href="https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#General_Recommendation:_Synchronizer_Token_Pattern">Synchronizer
|
||
Token Pattern</link>. 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.</para>
|
||
<para>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.</para>
|
||
<para>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:</para>
|
||
<programlisting><![CDATA[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>
|
||
]]></programlisting>
|
||
<para>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.</para>
|
||
</section>
|
||
<section>
|
||
<title>When to use CSRF protection</title>
|
||
<para>When 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.</para>
|
||
<section>
|
||
<title>CSRF protection and JSON</title>
|
||
<para>A common question is, but 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
|
||
<link xlink:href="http://blog.opensecurityresearch.com/2012/02/json-csrf-with-parameter-padding.html" >CSRF with JSON using the following form</link>:</para>
|
||
<programlisting language="xml"><![CDATA[<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>]]></programlisting>
|
||
<para>This will produce the following JSON structure</para>
|
||
<programlisting language="javascript"><![CDATA[{ "amount":100,
|
||
"routingNumber": "evilsRoutingNumber",
|
||
"account": "evilsAccountNumber",
|
||
"ignore_me": "=test"
|
||
}]]></programlisting>
|
||
<para>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:</para>
|
||
<programlisting language="xml"><![CDATA[<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>]]></programlisting>
|
||
</section>
|
||
<section>
|
||
<title>CSRF and Stateless Browser Applications</title>
|
||
<para>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.</para>
|
||
<para>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.</para>
|
||
<para>User's 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.</para>
|
||
</section>
|
||
</section>
|
||
<section xml:id="csrf-using">
|
||
<title>Using Spring Security CSRF Protection</title>
|
||
<para>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:</para>
|
||
<orderedlist inheritnum="ignore" continuation="restarts">
|
||
<listitem>
|
||
<para><link linkend="csrf-use-proper-verbs">Use proper HTTP verbs</link></para>
|
||
</listitem>
|
||
<listitem>
|
||
<para><link linkend="csrf-configure">Configure CSRF Protection</link></para>
|
||
</listitem>
|
||
<listitem>
|
||
<para><link linkend="csrf-include-csrf-token">Include the CSRF Token</link></para>
|
||
</listitem>
|
||
</orderedlist>
|
||
<section xml:id="csrf-use-proper-verbs">
|
||
<title>Use proper HTTP verbs</title>
|
||
<para>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.</para>
|
||
<para>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
|
||
<link xlink:href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec15.html#sec15.1.3">RFC 2616 Section 15.1.3 Encoding Sensitive Information in URI's</link> for
|
||
general guidance on using POST instead of GET for sensitive information.</para>
|
||
</section>
|
||
<section xml:id="csrf-configure">
|
||
<title>Configure CSRF Protection</title>
|
||
<para>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 <link linkend="csrf-logout">its own problems</link>. Instead by default Spring Security's CSRF protection will produce an HTTP 403 access denied.
|
||
This can be customized by configuring the <link linkend="access-denied-handler">AccessDeniedHandler</link> to process <classname>InvalidCsrfTokenException</classname>
|
||
differently.</para>
|
||
<para>For passivity reasons, if you are using the XML configuration, CSRF protection must be explicitly enabled using the <link linkend="nsa-csrf"><csrf></link> element. Refer to the
|
||
<link linkend="nsa-csrf"><csrf></link> element's documentation for additional customizations.</para>
|
||
<note>
|
||
<para><link xlink:href="https://jira.springsource.org/browse/SEC-2347">SEC-2347</link> is logged to ensure Spring
|
||
Security 4.x's XML namespace configuration will enable CSRF protection by default.</para>
|
||
</note>
|
||
<programlisting language="xml"><![CDATA[<http>
|
||
<!-- ... -->
|
||
<csrf />
|
||
</http>
|
||
]]></programlisting>
|
||
<para>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.</para>
|
||
<programlisting language="java"><![CDATA[@EnableWebSecurity
|
||
@Configuration
|
||
public class WebSecurityConfig extends
|
||
WebSecurityConfigurerAdapter {
|
||
|
||
@Override
|
||
protected void configure(HttpSecurity http) throws Exception {
|
||
http
|
||
.csrf().disable();
|
||
}
|
||
}]]></programlisting>
|
||
</section>
|
||
<section xml:id="csrf-include-csrf-token">
|
||
<title>Include the CSRF Token</title>
|
||
<section xml:id="csrf-include-csrf-token-form">
|
||
<title>Form Submissions</title>
|
||
<para>The last step is to ensure that you include the CSRF token in all PATCH, POST, PUT, and DELETE methods. This can be done using
|
||
the _csrf request attribute to obtain the current CsrfToken. An example of doing this with a JSP is shown below:</para>
|
||
<programlisting language="xml"><![CDATA[<c:url var="logoutUrl" value="/logout"/>
|
||
<form action="${logoutUrl}"
|
||
method="post">
|
||
<input type="submit"
|
||
value="Log out" />
|
||
<input type="hidden"
|
||
name="${_csrf.parameterName}"
|
||
value="${_csrf.token}"/>
|
||
</form>]]></programlisting>
|
||
<note>
|
||
<para>If you are using Spring MVC <form:form> tag, the <interfacename>CsrfToken</interfacename> is automatically included for you using the CsrfRequestDataValueProcessor.</para>
|
||
</note>
|
||
</section>
|
||
<section xml:id="csrf-include-csrf-token-ajax">
|
||
<title>Ajax and JSON Requests</title>
|
||
<para>If you 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:</para>
|
||
<programlisting language="xml"><![CDATA[<html>
|
||
<head>
|
||
<meta name="_csrf" content="${_csrf.token}"/>
|
||
<!-- default header name is X-CSRF-TOKEN -->
|
||
<meta name="_csrf_header" content="${_csrf.headerName}"/>
|
||
<!-- ... -->
|
||
</head>
|
||
<!-- ... -->]]></programlisting>
|
||
<para>You can then include the token within all your Ajax requests. If you were using jQuery, this could be done with the following:</para>
|
||
<programlisting language="javascript"><![CDATA[$(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);
|
||
});
|
||
});]]></programlisting>
|
||
<para>As a alternative to jQuery, we recommend using <ulink url="http://cujojs.com/">cujoJS’s</ulink> rest.js. <ulink url="https://github.com/cujojs/rest">rest.js</ulink> provides
|
||
advanced support for working with HTTP request 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.</para>
|
||
<programlisting language="javascript"><![CDATA[var client = rest.chain(csrf, {
|
||
token: $("meta[name='_csrf']").attr("content"),
|
||
name: $("meta[name='_csrf_header']").attr("content")
|
||
});]]></programlisting>
|
||
<para>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 different between rest.js
|
||
and jQuery is that only requests made with the configured client will contain the CSRF token, vs jQuery where <emphasis>all</emphasis> 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
|
||
<ulink url="https://github.com/cujojs/rest/tree/master/docs">rest.js reference documentation</ulink> for more information on rest.js.</para>
|
||
</section>
|
||
</section>
|
||
</section>
|
||
<section xml:id="csrf-caveats">
|
||
<title>CSRF Caveats</title>
|
||
<para>There are a few caveats when implementing CSRF.</para>
|
||
<section xml:id="csrf-timeouts">
|
||
<title>Timeouts</title>
|
||
<para>One issue is that the expected CSRF token is stored in the HttpSession, so as soon as the HttpSession expires your configured
|
||
<interfacename>AccessDeniedHandler</interfacename> will receive a InvalidCsrfTokenException. If you are using the default
|
||
<interfacename>AccessDeniedHandler</interfacename>, the browser will get an HTTP 403 and display a poor error message.</para>
|
||
<note>
|
||
<para>One might ask why the expected <interfacename>CsrfToken</interfacename> isn't stored in a cookie. 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
|
||
<link xlink:href="http://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</link>. See <link xlink:href="http://lists.webappsec.org/pipermail/websecurity_lists.webappsec.org/2011-February/007533.html">this webappsec.org thread</link>
|
||
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 something got compromised.</para>
|
||
</note>
|
||
<para>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.</para>
|
||
<para>Alternatively, specifying a custom <interfacename>AccessDeniedHandler</interfacename> allows you to process the <classname>InvalidCsrfTokenException</classname>
|
||
anyway you like. For an example of how to customize the <interfacename>AccessDeniedHandler</interfacename> refer to the provided links for both
|
||
<link linkend="#nsa-access-denied-handler">xml</link> and
|
||
<link xlink:href="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</link>.</para>
|
||
</section>
|
||
<section xml:id="csrf-login">
|
||
<title>Logging In</title>
|
||
<para>In order to protect against forging log in requests the log in form should be protected against CSRF attacks too. Since the <interfacename>CsrfToken</interfacename> is stored in
|
||
HttpSession, this means an HttpSession will be created as soon as <interfacename>CsrfToken</interfacename> 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.</para>
|
||
</section>
|
||
<section xml:id="csrf-logout">
|
||
<title>Logging Out</title>
|
||
<para>Adding CSRF will update the LogoutFilter to only use HTTP POST. This ensures that log out requires a CSRF token and that a malicious user cannot forcibly
|
||
log out your users.</para>
|
||
<para>One approach is to use a form for 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.</para>
|
||
</section>
|
||
<section xml:id="csrf-multipart">
|
||
<title>Multipart (file upload)</title>
|
||
<para>There are two options to using CSRF protection with multipart/form-data. Each option has its tradeoffs.
|
||
<orderedlist inheritnum="ignore" continuation="restarts">
|
||
<listitem>
|
||
<para><link linkend="csrf-multipartfilter">Placing MultipartFilter before Spring Security</link></para>
|
||
</listitem>
|
||
<listitem>
|
||
<para><link linkend="csrf-include-csrf-token-in-action">Include CSRF token in action</link></para>
|
||
</listitem>
|
||
</orderedlist>
|
||
<note>
|
||
<para>More information about using multipart forms with Spring can be found within the
|
||
<link xlink:href="http://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/mvc.html#mvc-multipart">17.10 Spring's multipart (file upload)
|
||
support</link> section of the Spring reference.</para>
|
||
</note></para>
|
||
<section xml:id="csrf-multipartfilter">
|
||
<title>Placing MultipartFilter before Spring Security</title>
|
||
<para>The first option is to ensure that the <classname>MultipartFilter</classname> is specified before the Spring
|
||
Security filter. Specifying the <classname>MultipartFilter</classname> after the Spring Security filter means that there is no authorization for invoking the
|
||
<classname>MultipartFilter</classname> 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.</para>
|
||
<para>To ensure <classname>MultipartFilter</classname> is specified before the Spring Security filter with java configuration, users can override beforeSpringSecurityFilterChain as
|
||
shown below:</para>
|
||
<programlisting language="java"><![CDATA[public class SecurityApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
|
||
|
||
@Override
|
||
protected void beforeSpringSecurityFilterChain(ServletContext servletContext) {
|
||
insertFilters(servletContext, new MultipartFilter());
|
||
}
|
||
}]]></programlisting>
|
||
<para>To ensure <classname>MultipartFilter</classname> is specified before the Spring Security filter with XML configuration, users can ensure the <filter-mapping> element
|
||
of the <classname>MultipartFilter</classname> is placed before the springSecurityFilterChain within the web.xml as shown below:</para>
|
||
<programlisting language="xml"><![CDATA[<filter>
|
||
<filter-name>MultipartFilter</filter-name>
|
||
<filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
|
||
</filter>
|
||
<filter>
|
||
<filter-name>springSecurityFilterChain</filter-name>
|
||
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
|
||
</filter>
|
||
<filter-mapping>
|
||
<filter-name>MultipartFilter</filter-name>
|
||
<servlet-name>/*</servlet-name>
|
||
</filter-mapping>
|
||
<filter-mapping>
|
||
<filter-name>springSecurityFilterChain</filter-name>
|
||
<url-pattern>/*</url-pattern>
|
||
</filter-mapping>
|
||
]]></programlisting>
|
||
</section>
|
||
<section xml:id="csrf-include-csrf-token-in-action">
|
||
<title>Include CSRF token in action</title>
|
||
<para>If allowing unauthorized users to upload temporariy files is not acceptable, an alternative is to place the <classname>MultipartFilter</classname> after the Spring Security
|
||
filter and include the CSRF as a query parameter in the action attribute of the form. An example with a jsp is shown below</para>
|
||
<programlisting language="xml"><![CDATA[<form action="./upload?${_csrf.parameterName}=${_csrf.token}" method="post" enctype="multipart/form-data">]]></programlisting>
|
||
<para>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
|
||
<link xlink:href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec15.html#sec15.1.3">RFC 2616 Section 15.1.3 Encoding Sensitive Information in URI's</link>.</para>
|
||
</section>
|
||
</section>
|
||
<section>
|
||
<title>HiddenHttpMethodFilter</title>
|
||
<para>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.</para>
|
||
<para>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.</para>
|
||
</section>
|
||
</section>
|
||
<section>
|
||
<title>Overriding Defaults</title>
|
||
<para>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.</para>
|
||
<para>For example, you can provide a custom CsrfTokenRepository to override the way in which the <interfacename>CsrfToken</interfacename> is stored.</para>
|
||
<para>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 <link linkend="nsa-csrf"><csrf></link>
|
||
documentation for details on how to make these customizations with XML and the <classname>CsrfConfigurer</classname> javadoc for details on how to make these
|
||
customizations when using Java configuration.</para>
|
||
</section>
|
||
</chapter>
|