Skip to content

Commit 98bdd32

Browse files
committed
SEC-2282: Add CSRF documentation to the reference manual
1 parent 33db440 commit 98bdd32

File tree

2 files changed

+204
-0
lines changed

2 files changed

+204
-0
lines changed

docs/manual/src/docbook/csrf.xml

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="csrf"
2+
xmlns:xlink="http://www.w3.org/1999/xlink">
3+
<info>
4+
<title>Cross Site Request Forgery (CSRF)</title>
5+
</info>
6+
<para>This section discusses Spring Security's <link xlink:href="http://en.wikipedia.org/wiki/Cross-site_request_forgery">
7+
Cross Site Request Forgery (CSRF)</link> support.</para>
8+
<section>
9+
<title>CSRF Attacks</title>
10+
<para>Before we discuss how Spring Security can protect applications from CSRF attacks, we will explain what a CSRF
11+
attack is. Let's take a look at a concrete example to get a better understanding.</para>
12+
<para>Assume that your bank's website provides a form that allows transferring money from the currently logged in user
13+
to another bank account. For example, the HTTP request might look like:</para>
14+
<programlisting><![CDATA[POST /transfer HTTP/1.1
15+
Host: bank.example.com
16+
Cookie: JSESSIONID=randomid; Domain=bank.example.com; Secure; HttpOnly
17+
Content-Type: application/x-www-form-urlencoded
18+
19+
amount=100.00&routingNumber=1234&account=9876
20+
]]></programlisting>
21+
<para>Now pretend you authenticate to your bank's website and then, without logging out, visit an evil website. The evil
22+
website contains an HTML page with the following form:</para>
23+
<programlisting language="xml"><![CDATA[<form action="https://bank.example.com/transfer" method="post">
24+
<input type="hidden"
25+
name="amount"
26+
value="100.00"/>
27+
<input type="hidden"
28+
name="routingNumber"
29+
value="evilsRoutingNumber"/>
30+
<input type="hidden"
31+
name="account"
32+
value="evilsAccountNumber"/>
33+
<input type="submit"
34+
value="Win Money!'/>
35+
</form>]]></programlisting>
36+
<para>You like to win money, so you click on the submit button. In the process, you have unintentionally transferred $100 to
37+
a malicious user. This happens because, while the evil website cannot see your cookies, the cookies associated with your
38+
bank are still sent along with the request.</para>
39+
<para>Worst yet, this whole process could have been automated using JavaScript. This means you didn't even need to click on the
40+
button. So how do we protect ourselves from such attacks?</para>
41+
</section>
42+
<section>
43+
<title>Synchronizer Token Pattern</title>
44+
<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
45+
means there is no way to reject requests coming from the evil website and allow requests coming from the bank's website. To
46+
protect against CSRF attacks we need to ensure there is something in the request that the evil site is unable to provide.</para>
47+
<para>One solution is to use the
48+
<link xlink:href="https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#General_Recommendation:_Synchronizer_Token_Pattern">Synchronizer
49+
Token Pattern</link>. This solution is to ensure that each request requires, in addition to our session cookie, a randomly
50+
generated token as an HTTP parameter. When a request is submitted, the server must look up the expected value for the parameter
51+
and compare it against the actual value in the request. If the values do not match, the request should fail.</para>
52+
<para>We can relax the expectations to only require the token for each HTTP request that updates state. This can be safely done
53+
since the same origin policy ensures the evil site cannot read the response. Additionally, we do not want to include the random
54+
token in HTTP GET as this can cause the tokens to be leaked.</para>
55+
<para>Let's take a look at how our example would change. Assume the randomly generated token is present in an HTTP parameter named
56+
_csrf. For example, the request to transfer money would look like this:</para>
57+
<programlisting><![CDATA[POST /transfer HTTP/1.1
58+
Host: bank.example.com
59+
Cookie: JSESSIONID=randomid; Domain=bank.example.com; Secure; HttpOnly
60+
Content-Type: application/x-www-form-urlencoded
61+
62+
amount=100.00&routingNumber=1234&account=9876&_csrf=<secure-random>
63+
]]></programlisting>
64+
<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
65+
correct value for the _csrf parameter (which must be explicitly provided on the evil website) and the transfer will fail when the
66+
server compares the actual token to the expected token.</para>
67+
</section>
68+
<section>
69+
<title>Using Spring Security CSRF Support</title>
70+
<para>So what are the steps necessary to use Spring Security's to protect our site against CSRF attacks? The steps to using Spring
71+
Security's CSRF protection are outlined below:</para>
72+
<orderedlist inheritnum="ignore" continuation="restarts">
73+
<listitem>
74+
<para><link xlink:href="#csrf-use-proper-verbs">Use proper HTTP verbs</link></para>
75+
</listitem>
76+
<listitem>
77+
<para><link xlink:href="#csrf-configure">Configure CSRF Protection</link></para>
78+
</listitem>
79+
<listitem>
80+
<para><link xlink:href="#csrf-include-csrf-token">Include the CSRF Token</link></para>
81+
</listitem>
82+
</orderedlist>
83+
<section xml:id="csrf-use-proper-verbs">
84+
<title>Use proper HTTP verbs</title>
85+
<para>The first step to protecting against CSRF attacks is to ensure your website uses proper HTTP verbs. Specifically, before Spring
86+
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
87+
that modifies state. This is not a limitation of Spring Security's support, but instead a general requirement for proper CSRF prevention.</para>
88+
</section>
89+
<section xml:id="csrf-configure">
90+
<title>Configure CSRF Protection</title>
91+
<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
92+
using the <link xlink:href="#nsa-csrf">&lt;csrf /&gt;</link> element:</para>
93+
<programlisting language="xml"><![CDATA[<http ...>
94+
...
95+
<csrf />
96+
</http>
97+
]]></programlisting>
98+
<para>CSRF protection is enabled by default with Java configuration. If you would like to disable CSRF, the corresponding Java configuration can be
99+
seen below:</para>
100+
<programlisting language="java"><![CDATA[@EnableWebSecurity
101+
@Configuration
102+
public class WebSecurityConfig extends
103+
WebSecurityConfigurerAdapter {
104+
105+
@Override
106+
protected void configure(HttpSecurity http) throws Exception {
107+
http
108+
.csrf().disable()
109+
...;
110+
}
111+
}]]></programlisting>
112+
</section>
113+
<section xml:id="csrf-include-csrf-token">
114+
<title>Include the CSRF Token</title>
115+
<section xml:id="csrf-include-csrf-token-form">
116+
<title>Form Submissions</title>
117+
<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
118+
the _csrf request attribute to obtain the current CsrfToken. An example of doing this with a JSP is shown below:</para>
119+
<programlisting language="xml"><![CDATA[<c:url var="logoutUrl" value="/logout"/>
120+
<form action="${logoutUrl}"
121+
method="post">
122+
<input type="submit"
123+
value="Log out" />
124+
<input type="hidden"
125+
name="${_csrf.parameterName}"
126+
value="${_csrf.token}"/>
127+
</form>]]></programlisting>
128+
<note>
129+
<para>If you are using Spring MVC &lt;form:form&gt; tag, the <interfacename>CsrfToken</interfacename> is automatically included for you using the CsrfRequestDataValueProcessor.</para>
130+
</note>
131+
</section>
132+
<section xml:id="csrf-include-csrf-token-ajax">
133+
<title>Ajax Requests</title>
134+
<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.
135+
A typical pattern would be to include the CSRF token within your meta tags. An example with a JSP is shown below:</para>
136+
<programlisting language="xml"><![CDATA[<html>
137+
<head>
138+
<meta name="_csrf" content="${_csrf.token}"/>
139+
<!-- default header name is X-CSRF-TOKEN -->
140+
<meta name="_csrf_header" content="${_csrf.headerName}"/>
141+
...
142+
</head>
143+
...]]></programlisting>
144+
<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>
145+
<programlisting language="javascript"><![CDATA[$(document).ajaxSend(function(e, xhr, options) {
146+
var token = $("meta[name='_csrf']").attr("content");
147+
var header = $("meta[name='_csrf_header']").attr("content");
148+
xhr.setRequestHeader(header, token);
149+
});]]></programlisting>
150+
</section>
151+
</section>
152+
</section>
153+
<section>
154+
<title>CSRF Caveats</title>
155+
<para>There are a few caveats when implementing CSRF.</para>
156+
<section>
157+
<title>Timeouts</title>
158+
<para>One issue is that the expected CSRF token is stored in the HttpSession, so as soon as the HttpSession expires your configured
159+
<interfacename>AccessDeniedHandler</interfacename> will receive a InvalidCsrfTokenException. If you are using the default
160+
<interfacename>AccessDeniedHandler</interfacename>, the browser will get an HTTP 403 and display a poor error message.</para>
161+
<note>
162+
<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
163+
(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
164+
to forcibly terminate the token if something got compromised.</para>
165+
</note>
166+
<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.
167+
The user can click a button to continue and refresh the session.</para>
168+
<para>Alternatively, specifying a custom <interfacename>AccessDeniedHandler</interfacename> allows you to process the <classname>InvalidCsrfTokenException</classname>
169+
anyway you like. For an example of how to customize the <interfacename>AccessDeniedHandler</interfacename> refer to the provided links for both xml and Java
170+
configuration.</para>
171+
</section>
172+
<section>
173+
<title>Logging In</title>
174+
<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
175+
HttpSession, this means an HttpSession will be created as soon as <interfacename>CsrfToken</interfacename> token attribute is accessed. While this sounds bad in
176+
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
177+
compromised. Practically speaking, the CSRF token is quite small in size and should have a negligible impact on our architecture.</para>
178+
</section>
179+
<section>
180+
<title>Logging Out</title>
181+
<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
182+
log out your users.</para>
183+
<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
184+
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>
185+
</section>
186+
<section>
187+
<title>HiddenHttpMethodFilter</title>
188+
<para>The HiddenHttpMethodFilter should be placed before the Spring Security filter. In general this is true, but it could have additional implications when
189+
protecting against CSRF attacks.</para>
190+
<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
191+
best practice to ensure it is placed before Spring Security's filters.</para>
192+
</section>
193+
</section>
194+
<section>
195+
<title>Overriding Defaults</title>
196+
<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>
197+
<para>For example, you can provide a custom CsrfTokenRepository to override the way in which the <interfacename>CsrfToken</interfacename> is stored.</para>
198+
<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
199+
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">&lt;csrf /&gt;</link>
200+
documentation for details on how to make these customizations with XML and the <classname>CsrfConfigurer</classname> javadoc for details on how to make these
201+
customizations when using Java configuration.</para>
202+
</section>
203+
</chapter>

docs/manual/src/docbook/index.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@
130130
<xi:include href="core-filters.xml"/>
131131
<xi:include href="basic-and-digest-auth.xml"/>
132132
<xi:include href="remember-me-authentication.xml"/>
133+
<xi:include href="csrf.xml"/>
133134
<xi:include href="session-mgmt.xml"/>
134135
<xi:include href="anon-auth-provider.xml"/>
135136
</part>

0 commit comments

Comments
 (0)