From cb060f0f340356ac71ed7db5399753edce278766 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Mon, 9 May 2011 23:45:54 +0000 Subject: [PATCH] Fixed #15258 - Ajax CSRF protection doesn't apply to PUT or DELETE requests Thanks to brodie for the report, and further input from tow21 This is a potentially backwards incompatible change - if you were doing PUT/DELETE requests and relying on the lack of protection, you will need to update your code, as noted in the releaste notes. git-svn-id: http://code.djangoproject.com/svn/django/trunk@16201 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/middleware/csrf.py | 13 +++++++--- docs/ref/contrib/csrf.txt | 25 +++++++++--------- docs/releases/1.4.txt | 12 +++++++++ tests/regressiontests/csrf_tests/tests.py | 31 +++++++++++++++++++++++ 4 files changed, 65 insertions(+), 16 deletions(-) diff --git a/django/middleware/csrf.py b/django/middleware/csrf.py index e4cab6c08f..37f92b1d94 100644 --- a/django/middleware/csrf.py +++ b/django/middleware/csrf.py @@ -107,7 +107,8 @@ class CsrfViewMiddleware(object): if getattr(callback, 'csrf_exempt', False): return None - if request.method == 'POST': + # Assume that anything not defined as 'safe' by RC2616 needs protection. + if request.method not in ('GET', 'HEAD', 'OPTIONS', 'TRACE'): if getattr(request, '_dont_enforce_csrf_checks', False): # Mechanism to turn off CSRF checks for test suite. It comes after # the creation of CSRF cookies, so that everything else continues to @@ -165,10 +166,14 @@ class CsrfViewMiddleware(object): ) return self._reject(request, REASON_NO_CSRF_COOKIE) - # check incoming token - request_csrf_token = request.POST.get('csrfmiddlewaretoken', '') + # check non-cookie token for match + request_csrf_token = "" + if request.method == "POST": + request_csrf_token = request.POST.get('csrfmiddlewaretoken', '') + if request_csrf_token == "": - # Fall back to X-CSRFToken, to make things easier for AJAX + # Fall back to X-CSRFToken, to make things easier for AJAX, + # and possible for PUT/DELETE request_csrf_token = request.META.get('HTTP_X_CSRFTOKEN', '') if not constant_time_compare(request_csrf_token, csrf_token): diff --git a/docs/ref/contrib/csrf.txt b/docs/ref/contrib/csrf.txt index b42dc26fbd..9441393c81 100644 --- a/docs/ref/contrib/csrf.txt +++ b/docs/ref/contrib/csrf.txt @@ -13,11 +13,13 @@ who visits the malicious site in their browser. A related type of attack, 'login CSRF', where an attacking site tricks a user's browser into logging into a site with someone else's credentials, is also covered. -The first defense against CSRF attacks is to ensure that GET requests are -side-effect free. POST requests can then be protected by following the steps -below. +The first defense against CSRF attacks is to ensure that GET requests (and other +'safe' methods, as defined by `9.1.1 Safe Methods, HTTP 1.1, RFC 2616`_) are +side-effect free. Requests via 'unsafe' methods, such as POST, PUT and DELETE, +can then be protected by following the steps below. .. _Cross Site Request Forgeries: http://www.squarefree.com/securitytips/web-developers.html#CSRF +.. _9.1.1 Safe Methods, HTTP 1.1, RFC 2616: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html How to use it ============= @@ -198,9 +200,9 @@ The CSRF protection is based on the following things: This part is done by the template tag. -3. For all incoming POST requests, a CSRF cookie must be present, and the - 'csrfmiddlewaretoken' field must be present and correct. If it isn't, the - user will get a 403 error. +3. For all incoming requests that are not using HTTP GET, HEAD, OPTIONS or + TRACE, a CSRF cookie must be present, and the 'csrfmiddlewaretoken' field + must be present and correct. If it isn't, the user will get a 403 error. This check is done by ``CsrfViewMiddleware``. @@ -215,12 +217,11 @@ The CSRF protection is based on the following things: This ensures that only forms that have originated from your Web site can be used to POST data back. -It deliberately only targets HTTP POST requests (and the corresponding POST -forms). GET requests ought never to have any potentially dangerous side effects -(see `9.1.1 Safe Methods, HTTP 1.1, RFC 2616`_), and so a CSRF attack with a GET -request ought to be harmless. - -.. _9.1.1 Safe Methods, HTTP 1.1, RFC 2616: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html +It deliberately ignores GET requests (and other requests that are defined as +'safe' by RFC 2616). These requests ought never to have any potentially +dangerous side effects , and so a CSRF attack with a GET request ought to be +harmless. RFC 2616 defines POST, PUT and DELETE as 'unsafe', and all other +methods are assumed to be unsafe, for maximum protection. Caching ======= diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt index 1b8510f461..be95d2f02b 100644 --- a/docs/releases/1.4.txt +++ b/docs/releases/1.4.txt @@ -214,3 +214,15 @@ you should add the following lines in your settings file:: Don't forget to escape characters that have a special meaning in a regular expression. + +CSRF protection extended to PUT and DELETE +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Previously, Django's :doc:`CSRF protection ` provided +protection against only POST requests. Since use of PUT and DELETE methods in +AJAX applications is becoming more common, we now protect all methods not +defined as safe by RFC 2616 i.e. we exempt GET, HEAD, OPTIONS and TRACE, and +enforce protection on everything. + +If you using PUT or DELETE methods in AJAX applications, please see the +:ref:`instructions about using AJAX and CSRF `. diff --git a/tests/regressiontests/csrf_tests/tests.py b/tests/regressiontests/csrf_tests/tests.py index 8dc3fbbc1b..3894de2c02 100644 --- a/tests/regressiontests/csrf_tests/tests.py +++ b/tests/regressiontests/csrf_tests/tests.py @@ -164,6 +164,37 @@ class CsrfViewMiddlewareTest(TestCase): req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) self.assertEqual(None, req2) + def test_put_and_delete_rejected(self): + """ + Tests that HTTP PUT and DELETE methods have protection + """ + req = TestingHttpRequest() + req.method = 'PUT' + req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + self.assertEqual(403, req2.status_code) + + req = TestingHttpRequest() + req.method = 'DELETE' + req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + self.assertEqual(403, req2.status_code) + + def test_put_and_delete_allowed(self): + """ + Tests that HTTP PUT and DELETE methods can get through with + X-CSRFToken and a cookie + """ + req = self._get_GET_csrf_cookie_request() + req.method = 'PUT' + req.META['HTTP_X_CSRFTOKEN'] = self._csrf_id + req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + self.assertEqual(None, req2) + + req = self._get_GET_csrf_cookie_request() + req.method = 'DELETE' + req.META['HTTP_X_CSRFTOKEN'] = self._csrf_id + req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + self.assertEqual(None, req2) + # Tests for the template tag method def test_token_node_no_csrf_cookie(self): """