summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoshua Kehn <josh@kehn.us>2015-08-31 22:32:03 -0400
committerTim Graham <timograham@gmail.com>2015-09-05 09:19:57 -0400
commitab26b65b2f178597be845e203eb698e193e3502e (patch)
tree0373fa71f7f14e4bd4b24efe36554741144209ba
parent48c420d99209b147b58d7b873d1b54f76249c8dc (diff)
Fixed #25334 -- Provided a way to allow cross-origin unsafe requests over HTTPS.
Added the CSRF_TRUSTED_ORIGINS setting which contains a list of other domains that are included during the CSRF Referer header verification for secure (HTTPS) requests.
-rw-r--r--django/conf/global_settings.py1
-rw-r--r--django/middleware/csrf.py13
-rw-r--r--docs/ref/csrf.txt4
-rw-r--r--docs/ref/settings.txt18
-rw-r--r--docs/releases/1.9.txt3
-rw-r--r--docs/spelling_wordlist1
-rw-r--r--tests/csrf_tests/tests.py13
7 files changed, 48 insertions, 5 deletions
diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
index 280bfeb167..6b759d8454 100644
--- a/django/conf/global_settings.py
+++ b/django/conf/global_settings.py
@@ -558,6 +558,7 @@ CSRF_COOKIE_PATH = '/'
CSRF_COOKIE_SECURE = False
CSRF_COOKIE_HTTPONLY = False
CSRF_HEADER_NAME = 'HTTP_X_CSRFTOKEN'
+CSRF_TRUSTED_ORIGINS = []
############
# MESSAGES #
diff --git a/django/middleware/csrf.py b/django/middleware/csrf.py
index 6d16a92bf5..dee5bb1d93 100644
--- a/django/middleware/csrf.py
+++ b/django/middleware/csrf.py
@@ -19,7 +19,7 @@ from django.utils.http import same_origin
logger = logging.getLogger('django.request')
REASON_NO_REFERER = "Referer checking failed - no Referer."
-REASON_BAD_REFERER = "Referer checking failed - %s does not match %s."
+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."
@@ -154,10 +154,15 @@ class CsrfViewMiddleware(object):
if referer is None:
return self._reject(request, REASON_NO_REFERER)
+ # 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_referer = 'https://%s/' % request.get_host()
- if not same_origin(referer, good_referer):
- reason = REASON_BAD_REFERER % (referer, good_referer)
+ 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
return self._reject(request, reason)
if csrf_token is None:
diff --git a/docs/ref/csrf.txt b/docs/ref/csrf.txt
index 590990571f..ba24339a78 100644
--- a/docs/ref/csrf.txt
+++ b/docs/ref/csrf.txt
@@ -257,7 +257,8 @@ 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.)
+ reliable enough under HTTP.) Expanding the accepted referers beyond the
+ current host can be done with the :setting:`CSRF_TRUSTED_ORIGINS` setting.
This ensures that only forms that have originated from your Web site can be used
to POST data back.
@@ -460,3 +461,4 @@ A number of settings can be used to control Django's CSRF behavior:
* :setting:`CSRF_COOKIE_SECURE`
* :setting:`CSRF_FAILURE_VIEW`
* :setting:`CSRF_HEADER_NAME`
+* :setting:`CSRF_TRUSTED_ORIGINS`
diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
index 217f54281d..ed5ac98947 100644
--- a/docs/ref/settings.txt
+++ b/docs/ref/settings.txt
@@ -428,6 +428,23 @@ any hyphens with underscores, and adding an ``'HTTP_'`` prefix to the name.
For example, if your client sends a ``'X-XSRF-TOKEN'`` header, the setting
should be ``'HTTP_X_XSRF_TOKEN'``.
+.. setting:: CSRF_TRUSTED_ORIGINS
+
+CSRF_TRUSTED_ORIGINS
+--------------------
+
+.. versionadded:: 1.9
+
+Default: ``[]`` (Empty list)
+
+A list of hosts which are trusted origins for unsafe requests (e.g. ``POST``).
+For a :meth:`secure <django.http.HttpRequest.is_secure>` unsafe
+request, Django's CSRF protection requires that the request have a ``Referer``
+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.
+
.. setting:: DATABASES
DATABASES
@@ -3374,6 +3391,7 @@ Security
* :setting:`CSRF_COOKIE_SECURE`
* :setting:`CSRF_FAILURE_VIEW`
* :setting:`CSRF_HEADER_NAME`
+ * :setting:`CSRF_TRUSTED_ORIGINS`
* :setting:`SECRET_KEY`
* :setting:`X_FRAME_OPTIONS`
diff --git a/docs/releases/1.9.txt b/docs/releases/1.9.txt
index 5c0e3b613c..cf7909057b 100644
--- a/docs/releases/1.9.txt
+++ b/docs/releases/1.9.txt
@@ -484,6 +484,9 @@ CSRF
* The request header's name used for CSRF authentication can be customized
with :setting:`CSRF_HEADER_NAME`.
+* The new :setting:`CSRF_TRUSTED_ORIGINS` setting provides a way to allow
+ cross-origin unsafe requests (e.g. ``POST``) over HTTPS.
+
Signals
^^^^^^^
diff --git a/docs/spelling_wordlist b/docs/spelling_wordlist
index 691ab2f4ef..1edaa68d49 100644
--- a/docs/spelling_wordlist
+++ b/docs/spelling_wordlist
@@ -644,6 +644,7 @@ refactoring
refactorings
refactors
referer
+referers
reflow
regex
regexes
diff --git a/tests/csrf_tests/tests.py b/tests/csrf_tests/tests.py
index 2d347ccdde..aaa9a6969a 100644
--- a/tests/csrf_tests/tests.py
+++ b/tests/csrf_tests/tests.py
@@ -352,6 +352,19 @@ class CsrfViewMiddlewareTest(SimpleTestCase):
req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
self.assertIsNone(req2)
+ @override_settings(ALLOWED_HOSTS=['www.example.com'], CSRF_TRUSTED_ORIGINS=['dashboard.example.com'])
+ def test_https_csrf_trusted_origin_allowed(self):
+ """
+ A POST HTTPS request with a referer added to the CSRF_TRUSTED_ORIGINS
+ setting is accepted.
+ """
+ req = self._get_POST_request_with_token()
+ req._is_secure_override = True
+ req.META['HTTP_HOST'] = 'www.example.com'
+ req.META['HTTP_REFERER'] = 'https://dashboard.example.com'
+ req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
+ self.assertIsNone(req2)
+
def test_ensures_csrf_cookie_no_middleware(self):
"""
Tests that ensures_csrf_cookie decorator fulfils its promise