mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-03-01 19:09:08 +00:00
SEC-2282: Add CSRF documentation to the reference manual
This commit is contained in:
parent
33db440961
commit
98bdd32ca0
203
docs/manual/src/docbook/csrf.xml
Normal file
203
docs/manual/src/docbook/csrf.xml
Normal file
@ -0,0 +1,203 @@
|
||||
<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>Using Spring Security CSRF Support</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 xlink:href="#csrf-use-proper-verbs">Use proper HTTP verbs</link></para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para><link xlink:href="#csrf-configure">Configure CSRF Protection</link></para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para><link xlink:href="#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. This is not a limitation of Spring Security's support, but instead a general requirement for proper CSRF prevention.</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. If you are using the XML configuration, this can be done
|
||||
using the <link xlink:href="#nsa-csrf"><csrf /></link> element:</para>
|
||||
<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:</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 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[$(document).ajaxSend(function(e, xhr, options) {
|
||||
var token = $("meta[name='_csrf']").attr("content");
|
||||
var header = $("meta[name='_csrf_header']").attr("content");
|
||||
xhr.setRequestHeader(header, token);
|
||||
});]]></programlisting>
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
<section>
|
||||
<title>CSRF Caveats</title>
|
||||
<para>There are a few caveats when implementing CSRF.</para>
|
||||
<section>
|
||||
<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 <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. 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 xml and Java
|
||||
configuration.</para>
|
||||
</section>
|
||||
<section>
|
||||
<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>
|
||||
<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>
|
||||
<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 xlink:href="#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>
|
@ -130,6 +130,7 @@
|
||||
<xi:include href="core-filters.xml"/>
|
||||
<xi:include href="basic-and-digest-auth.xml"/>
|
||||
<xi:include href="remember-me-authentication.xml"/>
|
||||
<xi:include href="csrf.xml"/>
|
||||
<xi:include href="session-mgmt.xml"/>
|
||||
<xi:include href="anon-auth-provider.xml"/>
|
||||
</part>
|
||||
|
Loading…
x
Reference in New Issue
Block a user