Loading django/http/request.py +2 −9 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ from django.utils.datastructures import ImmutableList, MultiValueDict from django.utils.encoding import ( escape_uri_path, force_bytes, force_str, force_text, iri_to_uri, ) from django.utils.http import is_same_domain from django.utils.six.moves.urllib.parse import ( parse_qsl, quote, urlencode, urljoin, urlsplit, ) Loading Loading @@ -546,15 +547,7 @@ def validate_host(host, allowed_hosts): host = host[:-1] if host.endswith('.') else host for pattern in allowed_hosts: pattern = pattern.lower() match = ( pattern == '*' or pattern.startswith('.') and ( host.endswith(pattern) or host == pattern[1:] ) or pattern == host ) if match: if pattern == '*' or is_same_domain(host, pattern): return True return False django/middleware/csrf.py +29 −6 Original line number Diff line number Diff line Loading @@ -14,7 +14,8 @@ from django.core.urlresolvers import get_callable from django.utils.cache import patch_vary_headers from django.utils.crypto import constant_time_compare, get_random_string from django.utils.encoding import force_text from django.utils.http import same_origin from django.utils.http import is_same_domain from django.utils.six.moves.urllib.parse import urlparse logger = logging.getLogger('django.request') Loading @@ -22,6 +23,8 @@ REASON_NO_REFERER = "Referer checking failed - no Referer." REASON_BAD_REFERER = "Referer checking failed - %s does not match any trusted origins." REASON_NO_CSRF_COOKIE = "CSRF cookie not set." REASON_BAD_TOKEN = "CSRF token missing or incorrect." REASON_MALFORMED_REFERER = "Referer checking failed - Referer is malformed." REASON_INSECURE_REFERER = "Referer checking failed - Referer is insecure while host is secure." CSRF_KEY_LENGTH = 32 Loading Loading @@ -154,15 +157,35 @@ class CsrfViewMiddleware(object): if referer is None: return self._reject(request, REASON_NO_REFERER) referer = urlparse(referer) # Make sure we have a valid URL for Referer. if '' in (referer.scheme, referer.netloc): return self._reject(request, REASON_MALFORMED_REFERER) # Ensure that our Referer is also secure. if referer.scheme != 'https': return self._reject(request, REASON_INSECURE_REFERER) # If there isn't a CSRF_COOKIE_DOMAIN, assume we need an exact # match on host:port. If not, obey the cookie rules. if settings.CSRF_COOKIE_DOMAIN is None: # request.get_host() includes the port. good_referer = request.get_host() else: good_referer = settings.CSRF_COOKIE_DOMAIN server_port = request.META['SERVER_PORT'] if server_port not in ('443', '80'): good_referer = '%s:%s' % (good_referer, server_port) # Here we generate a list of all acceptable HTTP referers, # including the current host since that has been validated # upstream. good_hosts = list(settings.CSRF_TRUSTED_ORIGINS) # Note that request.get_host() includes the port. good_hosts.append(request.get_host()) good_referers = ['https://{0}/'.format(host) for host in good_hosts] if not any(same_origin(referer, host) for host in good_referers): reason = REASON_BAD_REFERER % referer good_hosts.append(good_referer) if not any(is_same_domain(referer.netloc, host) for host in good_hosts): reason = REASON_BAD_REFERER % referer.geturl() return self._reject(request, reason) if csrf_token is None: Loading django/utils/http.py +14 −8 Original line number Diff line number Diff line Loading @@ -253,18 +253,24 @@ def quote_etag(etag): return '"%s"' % etag.replace('\\', '\\\\').replace('"', '\\"') def same_origin(url1, url2): def is_same_domain(host, pattern): """ Checks if two URLs are 'same-origin' Return ``True`` if the host is either an exact match or a match to the wildcard pattern. Any pattern beginning with a period matches a domain and all of its subdomains. (e.g. ``.example.com`` matches ``example.com`` and ``foo.example.com``). Anything else is an exact string match. """ p1, p2 = urlparse(url1), urlparse(url2) try: o1 = (p1.scheme, p1.hostname, p1.port or PROTOCOL_TO_PORT[p1.scheme]) o2 = (p2.scheme, p2.hostname, p2.port or PROTOCOL_TO_PORT[p2.scheme]) return o1 == o2 except (ValueError, KeyError): if not pattern: return False pattern = pattern.lower() return ( pattern[0] == '.' and (host.endswith(pattern) or host == pattern[1:]) or pattern == host ) def is_safe_url(url, host=None): """ Loading docs/ref/csrf.txt +16 −4 Original line number Diff line number Diff line Loading @@ -257,11 +257,19 @@ The CSRF protection is based on the following things: due to the fact that HTTP 'Set-Cookie' headers are (unfortunately) accepted by clients that are talking to a site under HTTPS. (Referer checking is not done for HTTP requests because the presence of the Referer header is not reliable enough under HTTP.) Expanding the accepted referers beyond the current host can be done with the :setting:`CSRF_TRUSTED_ORIGINS` setting. reliable enough under HTTP.) This ensures that only forms that have originated from your Web site can be used to POST data back. If the :setting:`CSRF_COOKIE_DOMAIN` setting is set, the referer is compared against it. This setting supports subdomains. For example, ``CSRF_COOKIE_DOMAIN = '.example.com'`` will allow POST requests from ``www.example.com`` and ``api.example.com``. If the setting is not set, then the referer must match the HTTP ``Host`` header. Expanding the accepted referers beyond the current host or cookie domain can be done with the :setting:`CSRF_TRUSTED_ORIGINS` setting. This ensures that only forms that have originated from trusted domains can be used to POST data back. It deliberately ignores GET requests (and other requests that are defined as 'safe' by :rfc:`2616`). These requests ought never to have any potentially Loading @@ -269,6 +277,10 @@ dangerous side effects , and so a CSRF attack with a GET request ought to be harmless. :rfc:`2616` defines POST, PUT and DELETE as 'unsafe', and all other methods are assumed to be unsafe, for maximum protection. .. versionchanged:: 1.9 Checking against the :setting:`CSRF_COOKIE_DOMAIN` setting was added. Caching ======= Loading docs/ref/settings.txt +2 −0 Original line number Diff line number Diff line Loading @@ -444,6 +444,8 @@ header that matches the origin present in the ``Host`` header. This prevents, for example, a ``POST`` request from ``subdomain.example.com`` from succeeding against ``api.example.com``. If you need cross-origin unsafe requests over HTTPS, continuing the example, add ``"subdomain.example.com"`` to this list. The setting also supports subdomains, so you could add ``".example.com"``, for example, to allow access from all subdomains of ``example.com``. .. setting:: DATABASES Loading Loading
django/http/request.py +2 −9 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ from django.utils.datastructures import ImmutableList, MultiValueDict from django.utils.encoding import ( escape_uri_path, force_bytes, force_str, force_text, iri_to_uri, ) from django.utils.http import is_same_domain from django.utils.six.moves.urllib.parse import ( parse_qsl, quote, urlencode, urljoin, urlsplit, ) Loading Loading @@ -546,15 +547,7 @@ def validate_host(host, allowed_hosts): host = host[:-1] if host.endswith('.') else host for pattern in allowed_hosts: pattern = pattern.lower() match = ( pattern == '*' or pattern.startswith('.') and ( host.endswith(pattern) or host == pattern[1:] ) or pattern == host ) if match: if pattern == '*' or is_same_domain(host, pattern): return True return False
django/middleware/csrf.py +29 −6 Original line number Diff line number Diff line Loading @@ -14,7 +14,8 @@ from django.core.urlresolvers import get_callable from django.utils.cache import patch_vary_headers from django.utils.crypto import constant_time_compare, get_random_string from django.utils.encoding import force_text from django.utils.http import same_origin from django.utils.http import is_same_domain from django.utils.six.moves.urllib.parse import urlparse logger = logging.getLogger('django.request') Loading @@ -22,6 +23,8 @@ REASON_NO_REFERER = "Referer checking failed - no Referer." REASON_BAD_REFERER = "Referer checking failed - %s does not match any trusted origins." REASON_NO_CSRF_COOKIE = "CSRF cookie not set." REASON_BAD_TOKEN = "CSRF token missing or incorrect." REASON_MALFORMED_REFERER = "Referer checking failed - Referer is malformed." REASON_INSECURE_REFERER = "Referer checking failed - Referer is insecure while host is secure." CSRF_KEY_LENGTH = 32 Loading Loading @@ -154,15 +157,35 @@ class CsrfViewMiddleware(object): if referer is None: return self._reject(request, REASON_NO_REFERER) referer = urlparse(referer) # Make sure we have a valid URL for Referer. if '' in (referer.scheme, referer.netloc): return self._reject(request, REASON_MALFORMED_REFERER) # Ensure that our Referer is also secure. if referer.scheme != 'https': return self._reject(request, REASON_INSECURE_REFERER) # If there isn't a CSRF_COOKIE_DOMAIN, assume we need an exact # match on host:port. If not, obey the cookie rules. if settings.CSRF_COOKIE_DOMAIN is None: # request.get_host() includes the port. good_referer = request.get_host() else: good_referer = settings.CSRF_COOKIE_DOMAIN server_port = request.META['SERVER_PORT'] if server_port not in ('443', '80'): good_referer = '%s:%s' % (good_referer, server_port) # Here we generate a list of all acceptable HTTP referers, # including the current host since that has been validated # upstream. good_hosts = list(settings.CSRF_TRUSTED_ORIGINS) # Note that request.get_host() includes the port. good_hosts.append(request.get_host()) good_referers = ['https://{0}/'.format(host) for host in good_hosts] if not any(same_origin(referer, host) for host in good_referers): reason = REASON_BAD_REFERER % referer good_hosts.append(good_referer) if not any(is_same_domain(referer.netloc, host) for host in good_hosts): reason = REASON_BAD_REFERER % referer.geturl() return self._reject(request, reason) if csrf_token is None: Loading
django/utils/http.py +14 −8 Original line number Diff line number Diff line Loading @@ -253,18 +253,24 @@ def quote_etag(etag): return '"%s"' % etag.replace('\\', '\\\\').replace('"', '\\"') def same_origin(url1, url2): def is_same_domain(host, pattern): """ Checks if two URLs are 'same-origin' Return ``True`` if the host is either an exact match or a match to the wildcard pattern. Any pattern beginning with a period matches a domain and all of its subdomains. (e.g. ``.example.com`` matches ``example.com`` and ``foo.example.com``). Anything else is an exact string match. """ p1, p2 = urlparse(url1), urlparse(url2) try: o1 = (p1.scheme, p1.hostname, p1.port or PROTOCOL_TO_PORT[p1.scheme]) o2 = (p2.scheme, p2.hostname, p2.port or PROTOCOL_TO_PORT[p2.scheme]) return o1 == o2 except (ValueError, KeyError): if not pattern: return False pattern = pattern.lower() return ( pattern[0] == '.' and (host.endswith(pattern) or host == pattern[1:]) or pattern == host ) def is_safe_url(url, host=None): """ Loading
docs/ref/csrf.txt +16 −4 Original line number Diff line number Diff line Loading @@ -257,11 +257,19 @@ The CSRF protection is based on the following things: due to the fact that HTTP 'Set-Cookie' headers are (unfortunately) accepted by clients that are talking to a site under HTTPS. (Referer checking is not done for HTTP requests because the presence of the Referer header is not reliable enough under HTTP.) Expanding the accepted referers beyond the current host can be done with the :setting:`CSRF_TRUSTED_ORIGINS` setting. reliable enough under HTTP.) This ensures that only forms that have originated from your Web site can be used to POST data back. If the :setting:`CSRF_COOKIE_DOMAIN` setting is set, the referer is compared against it. This setting supports subdomains. For example, ``CSRF_COOKIE_DOMAIN = '.example.com'`` will allow POST requests from ``www.example.com`` and ``api.example.com``. If the setting is not set, then the referer must match the HTTP ``Host`` header. Expanding the accepted referers beyond the current host or cookie domain can be done with the :setting:`CSRF_TRUSTED_ORIGINS` setting. This ensures that only forms that have originated from trusted domains can be used to POST data back. It deliberately ignores GET requests (and other requests that are defined as 'safe' by :rfc:`2616`). These requests ought never to have any potentially Loading @@ -269,6 +277,10 @@ dangerous side effects , and so a CSRF attack with a GET request ought to be harmless. :rfc:`2616` defines POST, PUT and DELETE as 'unsafe', and all other methods are assumed to be unsafe, for maximum protection. .. versionchanged:: 1.9 Checking against the :setting:`CSRF_COOKIE_DOMAIN` setting was added. Caching ======= Loading
docs/ref/settings.txt +2 −0 Original line number Diff line number Diff line Loading @@ -444,6 +444,8 @@ header that matches the origin present in the ``Host`` header. This prevents, for example, a ``POST`` request from ``subdomain.example.com`` from succeeding against ``api.example.com``. If you need cross-origin unsafe requests over HTTPS, continuing the example, add ``"subdomain.example.com"`` to this list. The setting also supports subdomains, so you could add ``".example.com"``, for example, to allow access from all subdomains of ``example.com``. .. setting:: DATABASES Loading