/django/middleware/csrf.py
Python | 326 lines | 228 code | 28 blank | 70 comment | 16 complexity | aab9305924a206418844938b6c53a6c4 MD5 | raw file
1""" 2Cross Site Request Forgery Middleware. 3 4This module provides a middleware that implements protection 5against request forgeries from other sites. 6""" 7 8import itertools 9import re 10import random 11 12from django.conf import settings 13from django.core.urlresolvers import get_callable 14from django.utils.cache import patch_vary_headers 15from django.utils.hashcompat import md5_constructor 16from django.utils.http import same_origin 17from django.utils.log import getLogger 18from django.utils.safestring import mark_safe 19from django.utils.crypto import constant_time_compare 20 21_POST_FORM_RE = \ 22 re.compile(r'(<form\W[^>]*\bmethod\s*=\s*(\'|"|)POST(\'|"|)\b[^>]*>)', re.IGNORECASE) 23 24_HTML_TYPES = ('text/html', 'application/xhtml+xml') 25 26logger = getLogger('django.request') 27 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 34 35REASON_NO_REFERER = "Referer checking failed - no Referer." 36REASON_BAD_REFERER = "Referer checking failed - %s does not match %s." 37REASON_NO_COOKIE = "No CSRF or session cookie." 38REASON_NO_CSRF_COOKIE = "CSRF cookie not set." 39REASON_BAD_TOKEN = "CSRF token missing or incorrect." 40 41 42def _get_failure_view(): 43 """ 44 Returns the view to be used for CSRF rejections 45 """ 46 return get_callable(settings.CSRF_FAILURE_VIEW) 47 48 49def _get_new_csrf_key(): 50 return md5_constructor("%s%s" 51 % (randrange(0, _MAX_CSRF_KEY), settings.SECRET_KEY)).hexdigest() 52 53 54def _make_legacy_session_token(session_id): 55 return md5_constructor(settings.SECRET_KEY + session_id).hexdigest() 56 57 58def get_token(request): 59 """ 60 Returns the the CSRF token required for a POST form. The token is an 61 alphanumeric value. 62 63 A side effect of calling this function is to make the the csrf_protect 64 decorator and the CsrfViewMiddleware add a CSRF cookie and a 'Vary: Cookie' 65 header to the outgoing response. For this reason, you may need to use this 66 function lazily, as is done by the csrf context processor. 67 """ 68 request.META["CSRF_COOKIE_USED"] = True 69 return request.META.get("CSRF_COOKIE", None) 70 71 72def _sanitize_token(token): 73 # Allow only alphanum, and ensure we return a 'str' for the sake of the post 74 # processing middleware. 75 token = re.sub('[^a-zA-Z0-9]', '', str(token.decode('ascii', 'ignore'))) 76 if token == "": 77 # In case the cookie has been truncated to nothing at some point. 78 return _get_new_csrf_key() 79 else: 80 return token 81 82 83class CsrfViewMiddleware(object): 84 """ 85 Middleware that requires a present and correct csrfmiddlewaretoken 86 for POST requests that have a CSRF cookie, and sets an outgoing 87 CSRF cookie. 88 89 This middleware should be used in conjunction with the csrf_token template 90 tag. 91 """ 92 # The _accept and _reject methods currently only exist for the sake of the 93 # requires_csrf_token decorator. 94 def _accept(self, request): 95 # Avoid checking the request twice by adding a custom attribute to 96 # request. This will be relevant when both decorator and middleware 97 # are used. 98 request.csrf_processing_done = True 99 return None 100 101 def _reject(self, request, reason): 102 return _get_failure_view()(request, reason=reason) 103 104 def process_view(self, request, callback, callback_args, callback_kwargs): 105 106 if getattr(request, 'csrf_processing_done', False): 107 return None 108 109 # If the user doesn't have a CSRF cookie, generate one and store it in the 110 # request, so it's available to the view. We'll store it in a cookie when 111 # we reach the response. 112 try: 113 # In case of cookies from untrusted sources, we strip anything 114 # dangerous at this point, so that the cookie + token will have the 115 # same, sanitized value. 116 request.META["CSRF_COOKIE"] = _sanitize_token(request.COOKIES[settings.CSRF_COOKIE_NAME]) 117 cookie_is_new = False 118 except KeyError: 119 # No cookie, so create one. This will be sent with the next 120 # response. 121 request.META["CSRF_COOKIE"] = _get_new_csrf_key() 122 # Set a flag to allow us to fall back and allow the session id in 123 # place of a CSRF cookie for this request only. 124 cookie_is_new = True 125 126 # Wait until request.META["CSRF_COOKIE"] has been manipulated before 127 # bailing out, so that get_token still works 128 if getattr(callback, 'csrf_exempt', False): 129 return None 130 131 if request.method == 'POST': 132 if getattr(request, '_dont_enforce_csrf_checks', False): 133 # Mechanism to turn off CSRF checks for test suite. It comes after 134 # the creation of CSRF cookies, so that everything else continues to 135 # work exactly the same (e.g. cookies are sent etc), but before the 136 # any branches that call reject() 137 return self._accept(request) 138 139 if request.is_secure(): 140 # Suppose user visits http://example.com/ 141 # An active network attacker,(man-in-the-middle, MITM) sends a 142 # POST form which targets https://example.com/detonate-bomb/ and 143 # submits it via javascript. 144 # 145 # The attacker will need to provide a CSRF cookie and token, but 146 # that is no problem for a MITM and the session independent 147 # nonce we are using. So the MITM can circumvent the CSRF 148 # protection. This is true for any HTTP connection, but anyone 149 # using HTTPS expects better! For this reason, for 150 # https://example.com/ we need additional protection that treats 151 # http://example.com/ as completely untrusted. Under HTTPS, 152 # Barth et al. found that the Referer header is missing for 153 # same-domain requests in only about 0.2% of cases or less, so 154 # we can use strict Referer checking. 155 referer = request.META.get('HTTP_REFERER') 156 if referer is None: 157 logger.warning('Forbidden (%s): %s' % (REASON_NO_REFERER, request.path), 158 extra={ 159 'status_code': 403, 160 'request': request, 161 } 162 ) 163 return self._reject(request, REASON_NO_REFERER) 164 165 # Note that request.get_host() includes the port 166 good_referer = 'https://%s/' % request.get_host() 167 if not same_origin(referer, good_referer): 168 reason = REASON_BAD_REFERER % (referer, good_referer) 169 logger.warning('Forbidden (%s): %s' % (reason, request.path), 170 extra={ 171 'status_code': 403, 172 'request': request, 173 } 174 ) 175 return self._reject(request, reason) 176 177 # If the user didn't already have a CSRF cookie, then fall back to 178 # the Django 1.1 method (hash of session ID), so a request is not 179 # rejected if the form was sent to the user before upgrading to the 180 # Django 1.2 method (session independent nonce) 181 if cookie_is_new: 182 try: 183 session_id = request.COOKIES[settings.SESSION_COOKIE_NAME] 184 csrf_token = _make_legacy_session_token(session_id) 185 except KeyError: 186 # No CSRF cookie and no session cookie. For POST requests, 187 # we insist on a CSRF cookie, and in this way we can avoid 188 # all CSRF attacks, including login CSRF. 189 logger.warning('Forbidden (%s): %s' % (REASON_NO_COOKIE, request.path), 190 extra={ 191 'status_code': 403, 192 'request': request, 193 } 194 ) 195 return self._reject(request, REASON_NO_COOKIE) 196 else: 197 csrf_token = request.META["CSRF_COOKIE"] 198 199 # check incoming token 200 request_csrf_token = request.POST.get('csrfmiddlewaretoken', '') 201 if request_csrf_token == "": 202 # Fall back to X-CSRFToken, to make things easier for AJAX 203 request_csrf_token = request.META.get('HTTP_X_CSRFTOKEN', '') 204 205 if not constant_time_compare(request_csrf_token, csrf_token): 206 if cookie_is_new: 207 # probably a problem setting the CSRF cookie 208 logger.warning('Forbidden (%s): %s' % (REASON_NO_CSRF_COOKIE, request.path), 209 extra={ 210 'status_code': 403, 211 'request': request, 212 } 213 ) 214 return self._reject(request, REASON_NO_CSRF_COOKIE) 215 else: 216 logger.warning('Forbidden (%s): %s' % (REASON_BAD_TOKEN, request.path), 217 extra={ 218 'status_code': 403, 219 'request': request, 220 } 221 ) 222 return self._reject(request, REASON_BAD_TOKEN) 223 224 return self._accept(request) 225 226 def process_response(self, request, response): 227 if getattr(response, 'csrf_processing_done', False): 228 return response 229 230 # If CSRF_COOKIE is unset, then CsrfViewMiddleware.process_view was 231 # never called, probaby because a request middleware returned a response 232 # (for example, contrib.auth redirecting to a login page). 233 if request.META.get("CSRF_COOKIE") is None: 234 return response 235 236 if not request.META.get("CSRF_COOKIE_USED", False): 237 return response 238 239 # Set the CSRF cookie even if it's already set, so we renew the expiry timer. 240 response.set_cookie(settings.CSRF_COOKIE_NAME, 241 request.META["CSRF_COOKIE"], max_age = 60 * 60 * 24 * 7 * 52, 242 domain=settings.CSRF_COOKIE_DOMAIN) 243 # Content varies with the CSRF cookie, so set the Vary header. 244 patch_vary_headers(response, ('Cookie',)) 245 response.csrf_processing_done = True 246 return response 247 248 249class CsrfResponseMiddleware(object): 250 """ 251 DEPRECATED 252 Middleware that post-processes a response to add a csrfmiddlewaretoken. 253 254 This exists for backwards compatibility and as an interim measure until 255 applications are converted to using use the csrf_token template tag 256 instead. It will be removed in Django 1.4. 257 """ 258 def __init__(self): 259 import warnings 260 warnings.warn( 261 "CsrfResponseMiddleware and CsrfMiddleware are deprecated; use CsrfViewMiddleware and the template tag instead (see CSRF documentation).", 262 DeprecationWarning 263 ) 264 265 def process_response(self, request, response): 266 if getattr(response, 'csrf_exempt', False): 267 return response 268 269 if response['Content-Type'].split(';')[0] in _HTML_TYPES: 270 csrf_token = get_token(request) 271 # If csrf_token is None, we have no token for this request, which probably 272 # means that this is a response from a request middleware. 273 if csrf_token is None: 274 return response 275 276 # ensure we don't add the 'id' attribute twice (HTML validity) 277 idattributes = itertools.chain(("id='csrfmiddlewaretoken'",), 278 itertools.repeat('')) 279 def add_csrf_field(match): 280 """Returns the matched <form> tag plus the added <input> element""" 281 return mark_safe(match.group() + "<div style='display:none;'>" + \ 282 "<input type='hidden' " + idattributes.next() + \ 283 " name='csrfmiddlewaretoken' value='" + csrf_token + \ 284 "' /></div>") 285 286 # Modify any POST forms 287 response.content, n = _POST_FORM_RE.subn(add_csrf_field, response.content) 288 if n > 0: 289 # Content varies with the CSRF cookie, so set the Vary header. 290 patch_vary_headers(response, ('Cookie',)) 291 292 # Since the content has been modified, any Etag will now be 293 # incorrect. We could recalculate, but only if we assume that 294 # the Etag was set by CommonMiddleware. The safest thing is just 295 # to delete. See bug #9163 296 del response['ETag'] 297 return response 298 299 300class CsrfMiddleware(object): 301 """ 302 Django middleware that adds protection against Cross Site 303 Request Forgeries by adding hidden form fields to POST forms and 304 checking requests for the correct value. 305 306 CsrfMiddleware uses two middleware, CsrfViewMiddleware and 307 CsrfResponseMiddleware, which can be used independently. It is recommended 308 to use only CsrfViewMiddleware and use the csrf_token template tag in 309 templates for inserting the token. 310 """ 311 # We can't just inherit from CsrfViewMiddleware and CsrfResponseMiddleware 312 # because both have process_response methods. 313 def __init__(self): 314 self.response_middleware = CsrfResponseMiddleware() 315 self.view_middleware = CsrfViewMiddleware() 316 317 def process_response(self, request, resp): 318 # We must do the response post-processing first, because that calls 319 # get_token(), which triggers a flag saying that the CSRF cookie needs 320 # to be sent (done in CsrfViewMiddleware.process_response) 321 resp2 = self.response_middleware.process_response(request, resp) 322 return self.view_middleware.process_response(request, resp2) 323 324 def process_view(self, request, callback, callback_args, callback_kwargs): 325 return self.view_middleware.process_view(request, callback, callback_args, 326 callback_kwargs)