diff options
author | Arthur Koziel <arthur@arthurkoziel.com> | 2010-09-13 00:04:27 +0000 |
---|---|---|
committer | Arthur Koziel <arthur@arthurkoziel.com> | 2010-09-13 00:04:27 +0000 |
commit | dd49269c7db008b2567f50cb03c4d3d9b321daa1 (patch) | |
tree | 326dd25bb045ac016cda7966b43cbdfe1f67d699 /django/middleware/csrf.py | |
parent | c9b188c4ec939abbe48dae5a371276742e64b6b8 (diff) | |
download | django-soc2010/app-loading.tar.gz |
[soc2010/app-loading] merged trunkarchive/soc2010/app-loadingsoc2010/app-loading
git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2010/app-loading@13818 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django/middleware/csrf.py')
-rw-r--r-- | django/middleware/csrf.py | 69 |
1 files changed, 56 insertions, 13 deletions
diff --git a/django/middleware/csrf.py b/django/middleware/csrf.py index 9ca727fca9..5d3a871adb 100644 --- a/django/middleware/csrf.py +++ b/django/middleware/csrf.py @@ -27,22 +27,33 @@ else: randrange = random.randrange _MAX_CSRF_KEY = 18446744073709551616L # 2 << 63 +REASON_NO_REFERER = "Referer checking failed - no Referer." +REASON_BAD_REFERER = "Referer checking failed - %s does not match %s." +REASON_NO_COOKIE = "No CSRF or session cookie." +REASON_NO_CSRF_COOKIE = "CSRF cookie not set." +REASON_BAD_TOKEN = "CSRF token missing or incorrect." + + def _get_failure_view(): """ Returns the view to be used for CSRF rejections """ return get_callable(settings.CSRF_FAILURE_VIEW) + def _get_new_csrf_key(): return md5_constructor("%s%s" % (randrange(0, _MAX_CSRF_KEY), settings.SECRET_KEY)).hexdigest() + def _make_legacy_session_token(session_id): return md5_constructor(settings.SECRET_KEY + session_id).hexdigest() + def get_token(request): """ - Returns the the CSRF token required for a POST form. + Returns the the CSRF token required for a POST form. The token is an + alphanumeric value. A side effect of calling this function is to make the the csrf_protect decorator and the CsrfViewMiddleware add a CSRF cookie and a 'Vary: Cookie' @@ -52,6 +63,18 @@ def get_token(request): request.META["CSRF_COOKIE_USED"] = True return request.META.get("CSRF_COOKIE", None) + +def _sanitize_token(token): + # Allow only alphanum, and ensure we return a 'str' for the sake of the post + # processing middleware. + token = re.sub('[^a-zA-Z0-9]', '', str(token.decode('ascii', 'ignore'))) + if token == "": + # In case the cookie has been truncated to nothing at some point. + return _get_new_csrf_key() + else: + return token + + class CsrfViewMiddleware(object): """ Middleware that requires a present and correct csrfmiddlewaretoken @@ -62,9 +85,6 @@ class CsrfViewMiddleware(object): tag. """ def process_view(self, request, callback, callback_args, callback_kwargs): - if getattr(callback, 'csrf_exempt', False): - return None - if getattr(request, 'csrf_processing_done', False): return None @@ -80,7 +100,10 @@ class CsrfViewMiddleware(object): # request, so it's available to the view. We'll store it in a cookie when # we reach the response. try: - request.META["CSRF_COOKIE"] = request.COOKIES[settings.CSRF_COOKIE_NAME] + # In case of cookies from untrusted sources, we strip anything + # dangerous at this point, so that the cookie + token will have the + # same, sanitized value. + request.META["CSRF_COOKIE"] = _sanitize_token(request.COOKIES[settings.CSRF_COOKIE_NAME]) cookie_is_new = False except KeyError: # No cookie, so create one. This will be sent with the next @@ -90,6 +113,11 @@ class CsrfViewMiddleware(object): # place of a CSRF cookie for this request only. cookie_is_new = True + # Wait until request.META["CSRF_COOKIE"] has been manipulated before + # bailing out, so that get_token still works + if getattr(callback, 'csrf_exempt', False): + return None + if request.method == 'POST': if getattr(request, '_dont_enforce_csrf_checks', False): # Mechanism to turn off CSRF checks for test suite. It comes after @@ -124,16 +152,30 @@ class CsrfViewMiddleware(object): return accept() if request.is_secure(): - # Strict referer checking for HTTPS + # Suppose user visits http://example.com/ + # An active network attacker,(man-in-the-middle, MITM) sends a + # POST form which targets https://example.com/detonate-bomb/ and + # submits it via javascript. + # + # The attacker will need to provide a CSRF cookie and token, but + # that is no problem for a MITM and the session independent + # nonce we are using. So the MITM can circumvent the CSRF + # protection. This is true for any HTTP connection, but anyone + # using HTTPS expects better! For this reason, for + # https://example.com/ we need additional protection that treats + # http://example.com/ as completely untrusted. Under HTTPS, + # Barth et al. found that the Referer header is missing for + # same-domain requests in only about 0.2% of cases or less, so + # we can use strict Referer checking. referer = request.META.get('HTTP_REFERER') if referer is None: - return reject("Referer checking failed - no Referer.") + return reject(REASON_NO_REFERER) # The following check ensures that the referer is HTTPS, - # the domains match and the ports match. This might be too strict. + # the domains match and the ports match - the same origin policy. good_referer = 'https://%s/' % request.get_host() if not referer.startswith(good_referer): - return reject("Referer checking failed - %s does not match %s." % + return reject(REASON_BAD_REFERER % (referer, good_referer)) # If the user didn't already have a CSRF cookie, then fall back to @@ -148,7 +190,7 @@ class CsrfViewMiddleware(object): # No CSRF cookie and no session cookie. For POST requests, # we insist on a CSRF cookie, and in this way we can avoid # all CSRF attacks, including login CSRF. - return reject("No CSRF or session cookie.") + return reject(REASON_NO_COOKIE) else: csrf_token = request.META["CSRF_COOKIE"] @@ -157,9 +199,9 @@ class CsrfViewMiddleware(object): if request_csrf_token != csrf_token: if cookie_is_new: # probably a problem setting the CSRF cookie - return reject("CSRF cookie not set.") + return reject(REASON_NO_CSRF_COOKIE) else: - return reject("CSRF token missing or incorrect.") + return reject(REASON_BAD_TOKEN) return accept() @@ -185,6 +227,7 @@ class CsrfViewMiddleware(object): response.csrf_processing_done = True return response + class CsrfResponseMiddleware(object): """ DEPRECATED @@ -235,6 +278,7 @@ class CsrfResponseMiddleware(object): del response['ETag'] return response + class CsrfMiddleware(object): """ Django middleware that adds protection against Cross Site @@ -262,4 +306,3 @@ class CsrfMiddleware(object): def process_view(self, request, callback, callback_args, callback_kwargs): return self.view_middleware.process_view(request, callback, callback_args, callback_kwargs) - |