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
This commit is contained in:
Luke Plant 2011-05-09 23:45:54 +00:00
parent 8cbcf1d3a6
commit cb060f0f34
4 changed files with 65 additions and 16 deletions

View File

@ -107,7 +107,8 @@ class CsrfViewMiddleware(object):
if getattr(callback, 'csrf_exempt', False): if getattr(callback, 'csrf_exempt', False):
return None 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): if getattr(request, '_dont_enforce_csrf_checks', False):
# Mechanism to turn off CSRF checks for test suite. It comes after # Mechanism to turn off CSRF checks for test suite. It comes after
# the creation of CSRF cookies, so that everything else continues to # 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) return self._reject(request, REASON_NO_CSRF_COOKIE)
# check incoming token # check non-cookie token for match
request_csrf_token = ""
if request.method == "POST":
request_csrf_token = request.POST.get('csrfmiddlewaretoken', '') request_csrf_token = request.POST.get('csrfmiddlewaretoken', '')
if request_csrf_token == "": 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', '') request_csrf_token = request.META.get('HTTP_X_CSRFTOKEN', '')
if not constant_time_compare(request_csrf_token, csrf_token): if not constant_time_compare(request_csrf_token, csrf_token):

View File

@ -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 'login CSRF', where an attacking site tricks a user's browser into logging into
a site with someone else's credentials, is also covered. a site with someone else's credentials, is also covered.
The first defense against CSRF attacks is to ensure that GET requests are The first defense against CSRF attacks is to ensure that GET requests (and other
side-effect free. POST requests can then be protected by following the steps 'safe' methods, as defined by `9.1.1 Safe Methods, HTTP 1.1, RFC 2616`_) are
below. 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 .. _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 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. This part is done by the template tag.
3. For all incoming POST requests, a CSRF cookie must be present, and the 3. For all incoming requests that are not using HTTP GET, HEAD, OPTIONS or
'csrfmiddlewaretoken' field must be present and correct. If it isn't, the TRACE, a CSRF cookie must be present, and the 'csrfmiddlewaretoken' field
user will get a 403 error. must be present and correct. If it isn't, the user will get a 403 error.
This check is done by ``CsrfViewMiddleware``. 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 This ensures that only forms that have originated from your Web site can be used
to POST data back. to POST data back.
It deliberately only targets HTTP POST requests (and the corresponding POST It deliberately ignores GET requests (and other requests that are defined as
forms). GET requests ought never to have any potentially dangerous side effects 'safe' by RFC 2616). These requests ought never to have any potentially
(see `9.1.1 Safe Methods, HTTP 1.1, RFC 2616`_), and so a CSRF attack with a GET dangerous side effects , and so a CSRF attack with a GET request ought to be
request ought to be harmless. harmless. RFC 2616 defines POST, PUT and DELETE as 'unsafe', and all other
methods are assumed to be unsafe, for maximum protection.
.. _9.1.1 Safe Methods, HTTP 1.1, RFC 2616: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
Caching Caching
======= =======

View File

@ -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 Don't forget to escape characters that have a special meaning in a regular
expression. expression.
CSRF protection extended to PUT and DELETE
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Previously, Django's :doc:`CSRF protection </ref/contrib/csrf/>` 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 <csrf-ajax>`.

View File

@ -164,6 +164,37 @@ class CsrfViewMiddlewareTest(TestCase):
req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
self.assertEqual(None, req2) 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 # Tests for the template tag method
def test_token_node_no_csrf_cookie(self): def test_token_node_no_csrf_cookie(self):
""" """