OLD | NEW |
(Empty) | |
| 1 """ |
| 2 Cross Site Request Forgery Middleware. |
| 3 |
| 4 This module provides a middleware that implements protection |
| 5 against request forgeries from other sites. |
| 6 """ |
| 7 from __future__ import unicode_literals |
| 8 |
| 9 import logging |
| 10 import re |
| 11 import string |
| 12 |
| 13 from django.conf import settings |
| 14 from django.core.exceptions import ImproperlyConfigured |
| 15 from django.urls import get_callable |
| 16 from django.utils.cache import patch_vary_headers |
| 17 from django.utils.crypto import constant_time_compare, get_random_string |
| 18 from django.utils.deprecation import MiddlewareMixin |
| 19 from django.utils.encoding import force_text |
| 20 from django.utils.http import is_same_domain |
| 21 from django.utils.six.moves import zip |
| 22 from django.utils.six.moves.urllib.parse import urlparse |
| 23 |
| 24 logger = logging.getLogger('django.security.csrf') |
| 25 |
| 26 REASON_NO_REFERER = "Referer checking failed - no Referer." |
| 27 REASON_BAD_REFERER = "Referer checking failed - %s does not match any trusted or
igins." |
| 28 REASON_NO_CSRF_COOKIE = "CSRF cookie not set." |
| 29 REASON_BAD_TOKEN = "CSRF token missing or incorrect." |
| 30 REASON_MALFORMED_REFERER = "Referer checking failed - Referer is malformed." |
| 31 REASON_INSECURE_REFERER = "Referer checking failed - Referer is insecure while h
ost is secure." |
| 32 |
| 33 CSRF_SECRET_LENGTH = 32 |
| 34 CSRF_TOKEN_LENGTH = 2 * CSRF_SECRET_LENGTH |
| 35 CSRF_ALLOWED_CHARS = string.ascii_letters + string.digits |
| 36 CSRF_SESSION_KEY = '_csrftoken' |
| 37 |
| 38 |
| 39 def _get_failure_view(): |
| 40 """ |
| 41 Returns the view to be used for CSRF rejections |
| 42 """ |
| 43 return get_callable(settings.CSRF_FAILURE_VIEW) |
| 44 |
| 45 |
| 46 def _get_new_csrf_string(): |
| 47 return get_random_string(CSRF_SECRET_LENGTH, allowed_chars=CSRF_ALLOWED_CHAR
S) |
| 48 |
| 49 |
| 50 def _salt_cipher_secret(secret): |
| 51 """ |
| 52 Given a secret (assumed to be a string of CSRF_ALLOWED_CHARS), generate a |
| 53 token by adding a salt and using it to encrypt the secret. |
| 54 """ |
| 55 salt = _get_new_csrf_string() |
| 56 chars = CSRF_ALLOWED_CHARS |
| 57 pairs = zip((chars.index(x) for x in secret), (chars.index(x) for x in salt)
) |
| 58 cipher = ''.join(chars[(x + y) % len(chars)] for x, y in pairs) |
| 59 return salt + cipher |
| 60 |
| 61 |
| 62 def _unsalt_cipher_token(token): |
| 63 """ |
| 64 Given a token (assumed to be a string of CSRF_ALLOWED_CHARS, of length |
| 65 CSRF_TOKEN_LENGTH, and that its first half is a salt), use it to decrypt |
| 66 the second half to produce the original secret. |
| 67 """ |
| 68 salt = token[:CSRF_SECRET_LENGTH] |
| 69 token = token[CSRF_SECRET_LENGTH:] |
| 70 chars = CSRF_ALLOWED_CHARS |
| 71 pairs = zip((chars.index(x) for x in token), (chars.index(x) for x in salt)) |
| 72 secret = ''.join(chars[x - y] for x, y in pairs) # Note negative values are
ok |
| 73 return secret |
| 74 |
| 75 |
| 76 def _get_new_csrf_token(): |
| 77 return _salt_cipher_secret(_get_new_csrf_string()) |
| 78 |
| 79 |
| 80 def get_token(request): |
| 81 """ |
| 82 Returns the CSRF token required for a POST form. The token is an |
| 83 alphanumeric value. A new token is created if one is not already set. |
| 84 |
| 85 A side effect of calling this function is to make the csrf_protect |
| 86 decorator and the CsrfViewMiddleware add a CSRF cookie and a 'Vary: Cookie' |
| 87 header to the outgoing response. For this reason, you may need to use this |
| 88 function lazily, as is done by the csrf context processor. |
| 89 """ |
| 90 if "CSRF_COOKIE" not in request.META: |
| 91 csrf_secret = _get_new_csrf_string() |
| 92 request.META["CSRF_COOKIE"] = _salt_cipher_secret(csrf_secret) |
| 93 else: |
| 94 csrf_secret = _unsalt_cipher_token(request.META["CSRF_COOKIE"]) |
| 95 request.META["CSRF_COOKIE_USED"] = True |
| 96 return _salt_cipher_secret(csrf_secret) |
| 97 |
| 98 |
| 99 def rotate_token(request): |
| 100 """ |
| 101 Changes the CSRF token in use for a request - should be done on login |
| 102 for security purposes. |
| 103 """ |
| 104 request.META.update({ |
| 105 "CSRF_COOKIE_USED": True, |
| 106 "CSRF_COOKIE": _get_new_csrf_token(), |
| 107 }) |
| 108 request.csrf_cookie_needs_reset = True |
| 109 |
| 110 |
| 111 def _sanitize_token(token): |
| 112 # Allow only ASCII alphanumerics |
| 113 if re.search('[^a-zA-Z0-9]', force_text(token)): |
| 114 return _get_new_csrf_token() |
| 115 elif len(token) == CSRF_TOKEN_LENGTH: |
| 116 return token |
| 117 elif len(token) == CSRF_SECRET_LENGTH: |
| 118 # Older Django versions set cookies to values of CSRF_SECRET_LENGTH |
| 119 # alphanumeric characters. For backwards compatibility, accept |
| 120 # such values as unsalted secrets. |
| 121 # It's easier to salt here and be consistent later, rather than add |
| 122 # different code paths in the checks, although that might be a tad more |
| 123 # efficient. |
| 124 return _salt_cipher_secret(token) |
| 125 return _get_new_csrf_token() |
| 126 |
| 127 |
| 128 def _compare_salted_tokens(request_csrf_token, csrf_token): |
| 129 # Assume both arguments are sanitized -- that is, strings of |
| 130 # length CSRF_TOKEN_LENGTH, all CSRF_ALLOWED_CHARS. |
| 131 return constant_time_compare( |
| 132 _unsalt_cipher_token(request_csrf_token), |
| 133 _unsalt_cipher_token(csrf_token), |
| 134 ) |
| 135 |
| 136 |
| 137 class CsrfViewMiddleware(MiddlewareMixin): |
| 138 """ |
| 139 Middleware that requires a present and correct csrfmiddlewaretoken |
| 140 for POST requests that have a CSRF cookie, and sets an outgoing |
| 141 CSRF cookie. |
| 142 |
| 143 This middleware should be used in conjunction with the csrf_token template |
| 144 tag. |
| 145 """ |
| 146 # The _accept and _reject methods currently only exist for the sake of the |
| 147 # requires_csrf_token decorator. |
| 148 def _accept(self, request): |
| 149 # Avoid checking the request twice by adding a custom attribute to |
| 150 # request. This will be relevant when both decorator and middleware |
| 151 # are used. |
| 152 request.csrf_processing_done = True |
| 153 return None |
| 154 |
| 155 def _reject(self, request, reason): |
| 156 logger.warning( |
| 157 'Forbidden (%s): %s', reason, request.path, |
| 158 extra={ |
| 159 'status_code': 403, |
| 160 'request': request, |
| 161 } |
| 162 ) |
| 163 return _get_failure_view()(request, reason=reason) |
| 164 |
| 165 def _get_token(self, request): |
| 166 if settings.CSRF_USE_SESSIONS: |
| 167 try: |
| 168 return request.session.get(CSRF_SESSION_KEY) |
| 169 except AttributeError: |
| 170 raise ImproperlyConfigured( |
| 171 'CSRF_USE_SESSIONS is enabled, but request.session is not ' |
| 172 'set. SessionMiddleware must appear before CsrfViewMiddlewar
e ' |
| 173 'in MIDDLEWARE%s.' % ('_CLASSES' if settings.MIDDLEWARE is N
one else '') |
| 174 ) |
| 175 else: |
| 176 try: |
| 177 cookie_token = request.COOKIES[settings.CSRF_COOKIE_NAME] |
| 178 except KeyError: |
| 179 return None |
| 180 |
| 181 csrf_token = _sanitize_token(cookie_token) |
| 182 if csrf_token != cookie_token: |
| 183 # Cookie token needed to be replaced; |
| 184 # the cookie needs to be reset. |
| 185 request.csrf_cookie_needs_reset = True |
| 186 return csrf_token |
| 187 |
| 188 def _set_token(self, request, response): |
| 189 if settings.CSRF_USE_SESSIONS: |
| 190 request.session[CSRF_SESSION_KEY] = request.META['CSRF_COOKIE'] |
| 191 else: |
| 192 response.set_cookie( |
| 193 settings.CSRF_COOKIE_NAME, |
| 194 request.META['CSRF_COOKIE'], |
| 195 max_age=settings.CSRF_COOKIE_AGE, |
| 196 domain=settings.CSRF_COOKIE_DOMAIN, |
| 197 path=settings.CSRF_COOKIE_PATH, |
| 198 secure=settings.CSRF_COOKIE_SECURE, |
| 199 httponly=settings.CSRF_COOKIE_HTTPONLY, |
| 200 ) |
| 201 # Set the Vary header since content varies with the CSRF cookie. |
| 202 patch_vary_headers(response, ('Cookie',)) |
| 203 |
| 204 def process_request(self, request): |
| 205 csrf_token = self._get_token(request) |
| 206 if csrf_token is not None: |
| 207 # Use same token next time. |
| 208 request.META['CSRF_COOKIE'] = csrf_token |
| 209 |
| 210 def process_view(self, request, callback, callback_args, callback_kwargs): |
| 211 if getattr(request, 'csrf_processing_done', False): |
| 212 return None |
| 213 |
| 214 # Wait until request.META["CSRF_COOKIE"] has been manipulated before |
| 215 # bailing out, so that get_token still works |
| 216 if getattr(callback, 'csrf_exempt', False): |
| 217 return None |
| 218 |
| 219 # Assume that anything not defined as 'safe' by RFC7231 needs protection |
| 220 if request.method not in ('GET', 'HEAD', 'OPTIONS', 'TRACE'): |
| 221 if getattr(request, '_dont_enforce_csrf_checks', False): |
| 222 # Mechanism to turn off CSRF checks for test suite. |
| 223 # It comes after the creation of CSRF cookies, so that |
| 224 # everything else continues to work exactly the same |
| 225 # (e.g. cookies are sent, etc.), but before any |
| 226 # branches that call reject(). |
| 227 return self._accept(request) |
| 228 |
| 229 if request.is_secure(): |
| 230 # Suppose user visits http://example.com/ |
| 231 # An active network attacker (man-in-the-middle, MITM) sends a |
| 232 # POST form that targets https://example.com/detonate-bomb/ and |
| 233 # submits it via JavaScript. |
| 234 # |
| 235 # The attacker will need to provide a CSRF cookie and token, but |
| 236 # that's no problem for a MITM and the session-independent |
| 237 # secret we're using. So the MITM can circumvent the CSRF |
| 238 # protection. This is true for any HTTP connection, but anyone |
| 239 # using HTTPS expects better! For this reason, for |
| 240 # https://example.com/ we need additional protection that treats |
| 241 # http://example.com/ as completely untrusted. Under HTTPS, |
| 242 # Barth et al. found that the Referer header is missing for |
| 243 # same-domain requests in only about 0.2% of cases or less, so |
| 244 # we can use strict Referer checking. |
| 245 referer = force_text( |
| 246 request.META.get('HTTP_REFERER'), |
| 247 strings_only=True, |
| 248 errors='replace' |
| 249 ) |
| 250 if referer is None: |
| 251 return self._reject(request, REASON_NO_REFERER) |
| 252 |
| 253 referer = urlparse(referer) |
| 254 |
| 255 # Make sure we have a valid URL for Referer. |
| 256 if '' in (referer.scheme, referer.netloc): |
| 257 return self._reject(request, REASON_MALFORMED_REFERER) |
| 258 |
| 259 # Ensure that our Referer is also secure. |
| 260 if referer.scheme != 'https': |
| 261 return self._reject(request, REASON_INSECURE_REFERER) |
| 262 |
| 263 # If there isn't a CSRF_COOKIE_DOMAIN, require an exact match |
| 264 # match on host:port. If not, obey the cookie rules (or those |
| 265 # for the session cookie, if CSRF_USE_SESSIONS). |
| 266 good_referer = ( |
| 267 settings.SESSION_COOKIE_DOMAIN |
| 268 if settings.CSRF_USE_SESSIONS |
| 269 else settings.CSRF_COOKIE_DOMAIN |
| 270 ) |
| 271 if good_referer is not None: |
| 272 server_port = request.get_port() |
| 273 if server_port not in ('443', '80'): |
| 274 good_referer = '%s:%s' % (good_referer, server_port) |
| 275 else: |
| 276 # request.get_host() includes the port. |
| 277 good_referer = request.get_host() |
| 278 |
| 279 # Here we generate a list of all acceptable HTTP referers, |
| 280 # including the current host since that has been validated |
| 281 # upstream. |
| 282 good_hosts = list(settings.CSRF_TRUSTED_ORIGINS) |
| 283 good_hosts.append(good_referer) |
| 284 |
| 285 if not any(is_same_domain(referer.netloc, host) for host in good
_hosts): |
| 286 reason = REASON_BAD_REFERER % referer.geturl() |
| 287 return self._reject(request, reason) |
| 288 |
| 289 csrf_token = request.META.get('CSRF_COOKIE') |
| 290 if csrf_token is None: |
| 291 # No CSRF cookie. For POST requests, we insist on a CSRF cookie, |
| 292 # and in this way we can avoid all CSRF attacks, including login |
| 293 # CSRF. |
| 294 return self._reject(request, REASON_NO_CSRF_COOKIE) |
| 295 |
| 296 # Check non-cookie token for match. |
| 297 request_csrf_token = "" |
| 298 if request.method == "POST": |
| 299 try: |
| 300 request_csrf_token = request.POST.get('csrfmiddlewaretoken',
'') |
| 301 except IOError: |
| 302 # Handle a broken connection before we've completed reading |
| 303 # the POST data. process_view shouldn't raise any |
| 304 # exceptions, so we'll ignore and serve the user a 403 |
| 305 # (assuming they're still listening, which they probably |
| 306 # aren't because of the error). |
| 307 pass |
| 308 |
| 309 if request_csrf_token == "": |
| 310 # Fall back to X-CSRFToken, to make things easier for AJAX, |
| 311 # and possible for PUT/DELETE. |
| 312 request_csrf_token = request.META.get(settings.CSRF_HEADER_NAME,
'') |
| 313 |
| 314 request_csrf_token = _sanitize_token(request_csrf_token) |
| 315 if not _compare_salted_tokens(request_csrf_token, csrf_token): |
| 316 return self._reject(request, REASON_BAD_TOKEN) |
| 317 |
| 318 return self._accept(request) |
| 319 |
| 320 def process_response(self, request, response): |
| 321 if not getattr(request, 'csrf_cookie_needs_reset', False): |
| 322 if getattr(response, 'csrf_cookie_set', False): |
| 323 return response |
| 324 |
| 325 if not request.META.get("CSRF_COOKIE_USED", False): |
| 326 return response |
| 327 |
| 328 # Set the CSRF cookie even if it's already set, so we renew |
| 329 # the expiry timer. |
| 330 self._set_token(request, response) |
| 331 response.csrf_cookie_set = True |
| 332 return response |
OLD | NEW |