Ticket #10816: csrf-remove-session-dep-r10369.2.diff

File csrf-remove-session-dep-r10369.2.diff, 19.4 KB (added by Glenn, 6 years ago)

Add a prefix when using SECRET_KEY (see http://code.djangoproject.com/ticket/9977#comment:14)

  • django/contrib/csrf/middleware.py

     
    77
    88import re
    99import itertools
     10import random
    1011try:
    1112    from functools import wraps
    1213except ImportError:
     
    2425
    2526_HTML_TYPES = ('text/html', 'application/xhtml+xml')
    2627
    27 def _make_token(session_id):
    28     return md5_constructor(settings.SECRET_KEY + session_id).hexdigest()
     28# Use the system (hardware-based) random number generator if it exists.
     29if hasattr(random, 'SystemRandom'):
     30    randrange = random.SystemRandom().randrange
     31else:
     32    randrange = random.randrange
     33_MAX_CSRF_KEY = 18446744073709551616L     # 2 << 63
    2934
     35def _get_new_csrf_key():
     36    return md5_constructor("%s%s"
     37                % (randrange(0, _MAX_CSRF_KEY), settings.SECRET_KEY)).hexdigest()
     38
     39def _make_token(csrf_cookie):
     40    return md5_constructor("csrf-" + settings.SECRET_KEY + csrf_cookie).hexdigest()
     41
     42def _cookie_name():
     43    # Backwards-compatibility: derive the cookie name from SESSION_COOKIE_NAME, so if the
     44    # user has specified a unique session cookie, we use a unique CSRF cookie, too.
     45    return "csrf_%s" % settings.SESSION_COOKIE_NAME
     46
     47def get_csrf_token(request):
     48    """Return the CSRF token to be inserted into forms by CsrfResponseMiddleware."""
     49    csrf_cookie = request.META.get("CSRF_COOKIE", None)
     50    assert csrf_cookie is not None, 'META["CSRF_COOKIE"] is not set.'
     51
     52    return _make_token(csrf_cookie)
     53
    3054class CsrfViewMiddleware(object):
    3155    """
    3256    Middleware that requires a present and correct csrfmiddlewaretoken
    33     for POST requests that have an active session.
     57    for POST requests that have a CSRF cookie.
    3458    """
    3559    def process_view(self, request, callback, callback_args, callback_kwargs):
     60        if getattr(callback, 'csrf_exempt', False):
     61            return None
     62
     63        # If the user doesn't have a CSRF cookie, generate one and store it in the
     64        # request, so it's available to the view.  We'll store it in a cookie when
     65        # we reach the response.
     66        try:
     67            request.META["CSRF_COOKIE"] = request.COOKIES[_cookie_name()]
     68            cookie_is_new = False
     69        except KeyError:
     70            # No cookie, so create one.
     71            request.META["CSRF_COOKIE"] = _get_new_csrf_key()
     72            cookie_is_new = True
     73
    3674        if request.method == 'POST':
    37             if getattr(callback, 'csrf_exempt', False):
    38                 return None
    39 
    4075            if request.is_ajax():
    4176                return None
    4277
    43             try:
    44                 session_id = request.COOKIES[settings.SESSION_COOKIE_NAME]
    45             except KeyError:
    46                 # No session, no check required
    47                 return None
     78            # If the user didn't already have a CSRF key, then accept the session key for the middleware
     79            # token, so CSRF protection isn't lost for the period between upgrading to CSRF cookies to
     80            # the first time each user comes back to the site to receive one.
     81            if cookie_is_new:
     82                try:
     83                    csrf_cookie = request.COOKIES[settings.SESSION_COOKIE_NAME]
     84                except KeyError:
     85                    # No CSRF cookie and no session cookie; no check is performed.
     86                    return None
     87            else:
     88                csrf_cookie = request.META["CSRF_COOKIE"]
    4889
    49             csrf_token = _make_token(session_id)
     90            csrf_token = _make_token(csrf_cookie)
    5091            # check incoming token
    5192            try:
    5293                request_csrf_token = request.POST['csrfmiddlewaretoken']
     
    60101
    61102class CsrfResponseMiddleware(object):
    62103    """
    63     Middleware that post-processes a response to add a
    64     csrfmiddlewaretoken if the response/request have an active
    65     session.
     104    Middleware that post-processes a response to add a csrfmiddlewaretoken.
    66105    """
    67106    def process_response(self, request, response):
    68107        if getattr(response, 'csrf_exempt', False):
    69108            return response
    70109
    71         csrf_token = None
    72         try:
    73             # This covers a corner case in which the outgoing response
    74             # both contains a form and sets a session cookie.  This
    75             # really should not be needed, since it is best if views
    76             # that create a new session (login pages) also do a
    77             # redirect, as is done by all such view functions in
    78             # Django.
    79             cookie = response.cookies[settings.SESSION_COOKIE_NAME]
    80             csrf_token = _make_token(cookie.value)
    81         except KeyError:
    82             # Normal case - look for existing session cookie
    83             try:
    84                 session_id = request.COOKIES[settings.SESSION_COOKIE_NAME]
    85                 csrf_token = _make_token(session_id)
    86             except KeyError:
    87                 # no incoming or outgoing cookie
    88                 pass
     110        # Set the CSRF cookie even if it's already set, so we renew the expiry timer.
     111        response.set_cookie(_cookie_name(),
     112                request.META["CSRF_COOKIE"], max_age = 60 * 60 * 24 * 7 * 52,
     113                domain=settings.SESSION_COOKIE_DOMAIN,
     114                path=settings.SESSION_COOKIE_PATH)
    89115
    90         if csrf_token is not None and \
    91                 response['Content-Type'].split(';')[0] in _HTML_TYPES:
     116        if response['Content-Type'].split(';')[0] in _HTML_TYPES:
     117            csrf_token = get_csrf_token(request)
    92118
    93119            # ensure we don't add the 'id' attribute twice (HTML validity)
    94120            idattributes = itertools.chain(("id='csrfmiddlewaretoken'",),
     
    109135    Request Forgeries by adding hidden form fields to POST forms and
    110136    checking requests for the correct value.
    111137
    112     In the list of middlewares, SessionMiddleware is required, and
    113     must come after this middleware.  CsrfMiddleWare must come after
    114     compression middleware.
     138    CsrfMiddleWare must come after compression middleware.
    115139
    116     If a session ID cookie is present, it is hashed with the
     140    A persistent ID cookie is created.  This cookie is hashed with the
    117141    SECRET_KEY setting to create an authentication token.  This token
    118142    is added to all outgoing POST forms and is expected on all
    119     incoming POST requests that have a session ID cookie.
     143    incoming POST requests that have a CSRF ID cookie.
    120144
    121     If you are setting cookies directly, instead of using Django's
    122     session framework, this middleware will not work.
    123 
    124145    CsrfMiddleWare is composed of two middleware, CsrfViewMiddleware
    125146    and CsrfResponseMiddleware which can be used independently.
    126147    """
  • django/contrib/csrf/tests.py

     
    22
    33from django.test import TestCase
    44from django.http import HttpRequest, HttpResponse, HttpResponseForbidden
    5 from django.contrib.csrf.middleware import CsrfMiddleware, _make_token, csrf_exempt
     5from django.contrib.csrf.middleware import CsrfMiddleware, _make_token, _cookie_name, csrf_exempt
    66from django.conf import settings
    77
    88
     
    1717
    1818class CsrfMiddlewareTest(TestCase):
    1919
    20     _session_id = "1"
     20    _csrf_id = "1"
    2121
    22     def _get_GET_no_session_request(self):
     22    def _get_GET_no_csrf_cookie_request(self):
    2323        return HttpRequest()
    2424
    25     def _get_GET_session_request(self):
    26         req = self._get_GET_no_session_request()
    27         req.COOKIES[settings.SESSION_COOKIE_NAME] = self._session_id
     25    def _get_GET_csrf_cookie_request(self):
     26        req = self._get_GET_no_csrf_cookie_request()
     27        req.COOKIES[_cookie_name()] = self._csrf_id
    2828        return req
    2929
    30     def _get_POST_session_request(self):
    31         req = self._get_GET_session_request()
     30    def _get_POST_no_cookie_request(self):
     31        req = self._get_GET_no_csrf_cookie_request()
    3232        req.method = "POST"
    3333        return req
    3434
    35     def _get_POST_no_session_request(self):
    36         req = self._get_GET_no_session_request()
     35    def _get_POST_csrf_cookie_request(self):
     36        req = self._get_GET_csrf_cookie_request()
    3737        req.method = "POST"
    3838        return req
    3939
     40    def _get_POST_request_with_token(self):
     41        req = self._get_POST_csrf_cookie_request()
     42        req.POST['csrfmiddlewaretoken'] = _make_token(self._csrf_id)
     43        return req
     44
    4045    def _get_POST_session_request_with_token(self):
    41         req = self._get_POST_session_request()
    42         req.POST['csrfmiddlewaretoken'] = _make_token(self._session_id)
     46        req = self._get_POST_no_cookie_request()
     47        req.COOKIES[settings.SESSION_COOKIE_NAME] = self._csrf_id
     48        req.POST['csrfmiddlewaretoken'] = _make_token(self._csrf_id)
    4349        return req
    4450
     51    def _get_POST_session_request_no_token(self):
     52        req = self._get_POST_no_cookie_request()
     53        req.COOKIES[settings.SESSION_COOKIE_NAME] = self._csrf_id
     54        return req
     55
    4556    def _get_post_form_response(self):
    4657        return post_form_response()
    4758
    48     def _get_new_session_response(self):
    49         resp = self._get_post_form_response()
    50         resp.cookies[settings.SESSION_COOKIE_NAME] = self._session_id
     59    def _get_post_form_response_non_html(self):
     60        resp = post_form_response()
     61        resp["Content-Type"] = "application/xml"
    5162        return resp
    5263
    53     def _check_token_present(self, response):
    54         self.assertContains(response, "name='csrfmiddlewaretoken' value='%s'" % _make_token(self._session_id))
     64    def _check_token_present(self, response, csrf_id=None):
     65        self.assertContains(response, "name='csrfmiddlewaretoken' value='%s'" % _make_token(csrf_id or self._csrf_id))
    5566
    5667    def get_view(self):
    5768        return test_view
    5869
    5970    # Check the post processing
    60     def test_process_response_no_session(self):
     71    def test_process_response_existing_cookie(self):
    6172        """
    62         Check the the post-processor does nothing if no session active
     73        Check that the token is inserted when a prior CSRF cookie exists.
    6374        """
    64         req = self._get_GET_no_session_request()
     75        req = self._get_GET_csrf_cookie_request()
     76        CsrfMiddleware().process_view(req, self.get_view(), (), {})
     77
    6578        resp = self._get_post_form_response()
    6679        resp_content = resp.content # needed because process_response modifies resp
    6780        resp2 = CsrfMiddleware().process_response(req, resp)
    68         self.assertEquals(resp_content, resp2.content)
     81        self.assertNotEqual(resp_content, resp2.content)
     82        self._check_token_present(resp2)
    6983
    70     def test_process_response_existing_session(self):
     84    def test_process_response_existing_no_cookie(self):
    7185        """
    72         Check that the token is inserted if there is an existing session
     86        When no prior CSRF cookie exists, check that the cookie is created and a
     87        token is inserted.
    7388        """
    74         req = self._get_GET_session_request()
     89        req = self._get_GET_no_csrf_cookie_request()
     90        CsrfMiddleware().process_view(req, self.get_view(), (), {})
     91
    7592        resp = self._get_post_form_response()
    7693        resp_content = resp.content # needed because process_response modifies resp
    7794        resp2 = CsrfMiddleware().process_response(req, resp)
     95
     96        csrf_id = resp2.cookies.get(_cookie_name(), False)
     97        self.assertNotEqual(csrf_id, False)
    7898        self.assertNotEqual(resp_content, resp2.content)
    79         self._check_token_present(resp2)
     99        self._check_token_present(resp2, csrf_id.value)
    80100
    81     def test_process_response_new_session(self):
     101    def test_process_response_no_cookie(self):
    82102        """
    83         Check that the token is inserted if there is a new session being started
     103        Check the the post-processor does nothing for content-types not in _HTML_TYPES.
    84104        """
    85         req = self._get_GET_no_session_request() # no session in request
    86         resp = self._get_new_session_response() # but new session started
     105        req = self._get_GET_no_csrf_cookie_request()
     106        CsrfMiddleware().process_view(req, self.get_view(), (), {})
     107        resp = self._get_post_form_response_non_html()
    87108        resp_content = resp.content # needed because process_response modifies resp
    88109        resp2 = CsrfMiddleware().process_response(req, resp)
    89         self.assertNotEqual(resp_content, resp2.content)
    90         self._check_token_present(resp2)
     110        self.assertEquals(resp_content, resp2.content)
    91111
    92112    def test_process_response_exempt_view(self):
    93113        """
    94114        Check that no post processing is done for an exempt view
    95115        """
    96         req = self._get_POST_session_request()
     116        req = self._get_POST_csrf_cookie_request()
    97117        resp = csrf_exempt(self.get_view())(req)
    98118        resp_content = resp.content
    99119        resp2 = CsrfMiddleware().process_response(req, resp)
    100120        self.assertEquals(resp_content, resp2.content)
    101121
    102122    # Check the request processing
    103     def test_process_request_no_session(self):
     123    def test_process_request_session_cookie_no_csrf_cookie_token(self):
    104124        """
    105         Check that if no session is present, the middleware does nothing.
    106         to the incoming request.
     125        When no CSRF cookie exists, but the user has a session, check that a token
     126        using the session cookie as the CSRF cookie is accepted.
    107127        """
    108         req = self._get_POST_no_session_request()
     128        req = self._get_POST_session_request_with_token()
    109129        req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {})
    110130        self.assertEquals(None, req2)
     131    def test_process_request_session_cookie_no_csrf_cookie_no_token(self):
     132        """
     133        When no CSRF cookie exists, but the user has a session, check that a token
     134        using the session cookie as the CSRF cookie is accepted.
     135        """
     136        req = self._get_POST_session_request_no_token()
     137        req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {})
     138        self.assertEquals(HttpResponseForbidden, req2.__class__)
    111139
    112     def test_process_request_session_no_token(self):
     140    def test_process_request_no_cookie_no_token(self):
    113141        """
    114         Check that if a session is present but no token, we get a 'forbidden'
     142        Check that if no CSRF cookie is present, the middleware does nothing to
     143        the incoming request.
    115144        """
    116         req = self._get_POST_session_request()
     145        req = self._get_POST_no_cookie_request()
    117146        req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {})
     147        self.assertEquals(None, req2)
     148
     149    def test_process_request_cookie_no_token(self):
     150        """
     151        Check that if a cookie is present but no token, we get a 'forbidden'
     152        """
     153        req = self._get_POST_csrf_cookie_request()
     154        req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {})
    118155        self.assertEquals(HttpResponseForbidden, req2.__class__)
    119156
    120     def test_process_request_session_and_token(self):
     157    def test_process_request_cookie_and_token(self):
    121158        """
    122         Check that if a session is present and a token, the middleware lets it through
     159        Check that if both a cookie and a token is present, the middleware lets it through.
    123160        """
    124         req = self._get_POST_session_request_with_token()
     161        req = self._get_POST_request_with_token()
    125162        req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {})
    126163        self.assertEquals(None, req2)
    127164
    128     def test_process_request_session_no_token_exempt_view(self):
     165    def test_process_request_cookie_no_token_exempt_view(self):
    129166        """
    130         Check that if a session is present and no token, but the csrf_exempt
     167        Check that if a cookie is present and no token, but the csrf_exempt
    131168        decorator has been applied to the view, the middleware lets it through
    132169        """
    133         req = self._get_POST_session_request()
     170        req = self._get_POST_csrf_cookie_request()
    134171        req2 = CsrfMiddleware().process_view(req, csrf_exempt(self.get_view()), (), {})
    135172        self.assertEquals(None, req2)
    136173
     
    138175        """
    139176        Check that AJAX requests are automatically exempted.
    140177        """
    141         req = self._get_POST_session_request()
     178        req = self._get_POST_csrf_cookie_request()
    142179        req.META['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
    143180        req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {})
    144181        self.assertEquals(None, req2)
  • docs/ref/contrib/csrf.txt

     
    2323=============
    2424
    2525Add the middleware ``'django.contrib.csrf.middleware.CsrfMiddleware'`` to
    26 your list of middleware classes, :setting:`MIDDLEWARE_CLASSES`. It needs to process
    27 the response after the SessionMiddleware, so must come before it in the
    28 list. It also must process the response before things like compression
    29 happen to the response, so it must come after GZipMiddleware in the
    30 list.
     26your list of middleware classes, :setting:`MIDDLEWARE_CLASSES`. It must
     27process the response before things like compression happen to the response,
     28so it must come after GZipMiddleware in the list.
    3129
    3230The ``CsrfMiddleware`` class is actually composed of two middleware:
    3331``CsrfViewMiddleware`` which performs the checks on incoming requests,
     
    7068
    7169CsrfMiddleware does two things:
    7270
    73 1. It modifies outgoing requests by adding a hidden form field to all
     711. It stores a random number in a cookie.  For backwards-compatibility
     72   purposes, the cookie name is derived from the SESSION_COOKIE_NAME setting.
     73
     742. It modifies outgoing requests by adding a hidden form field to all
    7475   'POST' forms, with the name 'csrfmiddlewaretoken' and a value which is
    75    a hash of the session ID plus a secret. If there is no session ID set,
    76    this modification of the response isn't done, so there is very little
    77    performance penalty for those requests that don't have a session.
    78    (This is done by ``CsrfResponseMiddleware``).
     76   a hash of the cookie value plus a secret.  (This is done by
     77   ``CsrfResponseMiddleware``).
    7978
    80 2. On all incoming POST requests that have the session cookie set, it
    81    checks that the 'csrfmiddlewaretoken' is present and correct. If it
    82    isn't, the user will get a 403 error. (This is done by
    83    ``CsrfViewMiddleware``)
     793. On all incoming POST requests that have the cookie set, it checks that
     80   the 'csrfmiddlewaretoken' is present and correct. If it isn't, the user
     81   will get a 403 error. (This is done by ``CsrfViewMiddleware``)
    8482
    8583This ensures that only forms that have originated from your Web site
    8684can be used to POST data back.
     
    9088effects (see `9.1.1 Safe Methods, HTTP 1.1, RFC 2616`_), and so a
    9189CSRF attack with a GET request ought to be harmless.
    9290
    93 POST requests that are not accompanied by a session cookie are not protected,
    94 but they do not need to be protected, since the 'attacking' Web site
    95 could make these kind of requests anyway.
    96 
    9791The Content-Type is checked before modifying the response, and only
    9892pages that are served as 'text/html' or 'application/xml+xhtml'
    9993are modified.
     
    112106Limitations
    113107===========
    114108
    115 CsrfMiddleware requires Django's session framework to work. If you have
    116 a custom authentication system that manually sets cookies and the like,
    117 it won't help you.
    118 
    119109If your app creates HTML pages and forms in some unusual way, (e.g.
    120110it sends fragments of HTML in JavaScript document.write statements)
    121111you might bypass the filter that adds the hidden field to the form,
Back to Top