Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed a security issue in the CSRF component. Disclosure and new rele…

…ase forthcoming.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@15464 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 208630aa4b6460cb31cd81be2cc83d4075f1f27b 1 parent c2666c9
Alex Gaynor authored
30  django/middleware/csrf.py
@@ -101,6 +101,7 @@ def _reject(self, request, reason):
101 101
         return _get_failure_view()(request, reason=reason)
102 102
 
103 103
     def process_view(self, request, callback, callback_args, callback_kwargs):
  104
+
104 105
         if getattr(request, 'csrf_processing_done', False):
105 106
             return None
106 107
 
@@ -134,31 +135,6 @@ def process_view(self, request, callback, callback_args, callback_kwargs):
134 135
                 # any branches that call reject()
135 136
                 return self._accept(request)
136 137
 
137  
-            if request.is_ajax():
138  
-                # .is_ajax() is based on the presence of X-Requested-With.  In
139  
-                # the context of a browser, this can only be sent if using
140  
-                # XmlHttpRequest.  Browsers implement careful policies for
141  
-                # XmlHttpRequest:
142  
-                #
143  
-                #  * Normally, only same-domain requests are allowed.
144  
-                #
145  
-                #  * Some browsers (e.g. Firefox 3.5 and later) relax this
146  
-                #    carefully:
147  
-                #
148  
-                #    * if it is a 'simple' GET or POST request (which can
149  
-                #      include no custom headers), it is allowed to be cross
150  
-                #      domain.  These requests will not be recognized as AJAX.
151  
-                #
152  
-                #    * if a 'preflight' check with the server confirms that the
153  
-                #      server is expecting and allows the request, cross domain
154  
-                #      requests even with custom headers are allowed. These
155  
-                #      requests will be recognized as AJAX, but can only get
156  
-                #      through when the developer has specifically opted in to
157  
-                #      allowing the cross-domain POST request.
158  
-                #
159  
-                # So in all cases, it is safe to allow these requests through.
160  
-                return self._accept(request)
161  
-
162 138
             if request.is_secure():
163 139
                 # Suppose user visits http://example.com/
164 140
                 # An active network attacker,(man-in-the-middle, MITM) sends a
@@ -222,6 +198,10 @@ def process_view(self, request, callback, callback_args, callback_kwargs):
222 198
 
223 199
             # check incoming token
224 200
             request_csrf_token = request.POST.get('csrfmiddlewaretoken', '')
  201
+            if request_csrf_token == "":
  202
+                # Fall back to X-CSRFToken, to make things easier for AJAX
  203
+                request_csrf_token = request.META.get('HTTP_X_CSRFTOKEN', '')
  204
+
225 205
             if not constant_time_compare(request_csrf_token, csrf_token):
226 206
                 if cookie_is_new:
227 207
                     # probably a problem setting the CSRF cookie
63  docs/ref/contrib/csrf.txt
@@ -81,6 +81,47 @@ The utility script ``extras/csrf_migration_helper.py`` can help to automate the
81 81
 finding of code and templates that may need to be upgraded.  It contains full
82 82
 help on how to use it.
83 83
 
  84
+AJAX
  85
+----
  86
+
  87
+While the above method can be used for AJAX POST requests, it has some
  88
+inconveniences: you have to remember to pass the CSRF token in as POST data with
  89
+every POST request. For this reason, there is an alternative method: on each
  90
+XMLHttpRequest, set a custom `X-CSRFToken` header to the value of the CSRF
  91
+token. This is often easier, because many javascript frameworks provide hooks
  92
+that allow headers to be set on every request. In jQuery, you can use the
  93
+``beforeSend`` hook as follows:
  94
+
  95
+.. code-block:: javascript
  96
+
  97
+    $.ajaxSetup({
  98
+        beforeSend: function(xhr, settings) {
  99
+            function getCookie(name) {
  100
+                var cookieValue = null;
  101
+                if (document.cookie && document.cookie != '') {
  102
+                    var cookies = document.cookie.split(';');
  103
+                    for (var i = 0; i < cookies.length; i++) {
  104
+                        var cookie = jQuery.trim(cookies[i]);
  105
+                        // Does this cookie string begin with the name we want?
  106
+                        if (cookie.substring(0, name.length + 1) == (name + '=')) {
  107
+                            cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
  108
+                            break;
  109
+                        }
  110
+                    }
  111
+                }
  112
+                return cookieValue;
  113
+            }
  114
+            if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) {
  115
+                // Only send the token to relative URLs i.e. locally.
  116
+                xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
  117
+            }
  118
+        }
  119
+    });
  120
+
  121
+Adding this to a javascript file that is included on your site will ensure that
  122
+AJAX POST requests that are made via jQuery will not be caught by the CSRF
  123
+protection.
  124
+
84 125
 The decorator method
85 126
 --------------------
86 127
 
@@ -261,10 +302,6 @@ in the same module.  These disable the view protection mechanism
261 302
 (``CsrfResponseMiddleware``) respectively.  They can be used individually if
262 303
 required.
263 304
 
264  
-You don't have to worry about doing this for most AJAX views. Any request sent
265  
-with "X-Requested-With: XMLHttpRequest" is automatically exempt. (See the `How
266  
-it works`_ section.)
267  
-
268 305
 Subdomains
269 306
 ----------
270 307
 
@@ -342,24 +379,6 @@ request ought to be harmless.
342 379
 response, and only pages that are served as 'text/html' or
343 380
 'application/xml+xhtml' are modified.
344 381
 
345  
-AJAX
346  
-----
347  
-
348  
-The middleware tries to be smart about requests that come in via AJAX. Most
349  
-modern JavaScript toolkits send an "X-Requested-With: XMLHttpRequest" HTTP
350  
-header; these requests are detected and automatically *not* handled by this
351  
-middleware.  We can do this safely because, in the context of a browser, the
352  
-header can only be added by using ``XMLHttpRequest``, and browsers already
353  
-implement a same-domain policy for ``XMLHttpRequest``.
354  
-
355  
-For the more recent browsers that relax this same-domain policy, custom headers
356  
-like "X-Requested-With" are only allowed after the browser has done a
357  
-'preflight' check to the server to see if the cross-domain request is allowed,
358  
-using a strictly 'opt in' mechanism, so the exception for AJAX is still safe—if
359  
-the developer has specifically opted in to allowing cross-site AJAX POST
360  
-requests on a specific URL, they obviously don't want the middleware to disallow
361  
-exactly that.
362  
-
363 382
 .. _9.1.1 Safe Methods, HTTP 1.1, RFC 2616: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
364 383
 
365 384
 Caching
6  tests/regressiontests/csrf_tests/tests.py
@@ -284,12 +284,12 @@ def test_process_request_csrf_cookie_no_token_exempt_view(self):
284 284
         req2 = CsrfMiddleware().process_view(req, csrf_exempt(post_form_view), (), {})
285 285
         self.assertEquals(None, req2)
286 286
 
287  
-    def test_ajax_exemption(self):
  287
+    def test_csrf_token_in_header(self):
288 288
         """
289  
-        Check that AJAX requests are automatically exempted.
  289
+        Check that we can pass in the token in a header instead of in the form
290 290
         """
291 291
         req = self._get_POST_csrf_cookie_request()
292  
-        req.META['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
  292
+        req.META['HTTP_X_CSRFTOKEN'] = self._csrf_id
293 293
         req2 = CsrfMiddleware().process_view(req, post_form_view, (), {})
294 294
         self.assertEquals(None, req2)
295 295
 

0 notes on commit 208630a

Please sign in to comment.
Something went wrong with that request. Please try again.