Browse Source

SEC-2282: Add CSRF documentation to the reference manual

Rob Winch 12 năm trước cách đây
mục cha
commit
98bdd32ca0
2 tập tin đã thay đổi với 204 bổ sung0 xóa
  1. 203 0
      docs/manual/src/docbook/csrf.xml
  2. 1 0
      docs/manual/src/docbook/index.xml

+ 203 - 0
docs/manual/src/docbook/csrf.xml

@@ -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">&lt;csrf /&gt;</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 &lt;form:form&gt; 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">&lt;csrf /&gt;</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>

+ 1 - 0
docs/manual/src/docbook/index.xml

@@ -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>