mirror of
https://github.com/django/django.git
synced 2025-10-24 06:06:09 +00:00
Fixed #16010 -- Added Origin header checking to CSRF middleware.
Thanks David Benjamin for the original patch, and Florian Apolloner, Chris Jerdonek, and Adam Johnson for reviews.
This commit is contained in:
committed by
Mariusz Felisiak
parent
dba44a7a7a
commit
2411b8b5eb
@@ -5,7 +5,7 @@ from django.contrib.sessions.backends.cache import SessionStore
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.middleware.csrf import (
|
||||
CSRF_SESSION_KEY, CSRF_TOKEN_LENGTH, REASON_BAD_TOKEN,
|
||||
CSRF_SESSION_KEY, CSRF_TOKEN_LENGTH, REASON_BAD_ORIGIN, REASON_BAD_TOKEN,
|
||||
REASON_NO_CSRF_COOKIE, CsrfViewMiddleware,
|
||||
_compare_masked_tokens as equivalent_tokens, get_token,
|
||||
)
|
||||
@@ -510,6 +510,154 @@ class CsrfViewMiddlewareTestMixin:
|
||||
self.assertEqual(resp.status_code, 403)
|
||||
self.assertEqual(cm.records[0].getMessage(), 'Forbidden (%s): ' % REASON_BAD_TOKEN)
|
||||
|
||||
@override_settings(ALLOWED_HOSTS=['www.example.com'])
|
||||
def test_bad_origin_bad_domain(self):
|
||||
"""A request with a bad origin is rejected."""
|
||||
req = self._get_POST_request_with_token()
|
||||
req.META['HTTP_HOST'] = 'www.example.com'
|
||||
req.META['HTTP_ORIGIN'] = 'https://www.evil.org'
|
||||
mw = CsrfViewMiddleware(post_form_view)
|
||||
self.assertIs(mw._origin_verified(req), False)
|
||||
with self.assertLogs('django.security.csrf', 'WARNING') as cm:
|
||||
response = mw.process_view(req, post_form_view, (), {})
|
||||
self.assertEqual(response.status_code, 403)
|
||||
msg = REASON_BAD_ORIGIN % req.META['HTTP_ORIGIN']
|
||||
self.assertEqual(cm.records[0].getMessage(), 'Forbidden (%s): ' % msg)
|
||||
|
||||
@override_settings(ALLOWED_HOSTS=['www.example.com'])
|
||||
def test_bad_origin_null_origin(self):
|
||||
"""A request with a null origin is rejected."""
|
||||
req = self._get_POST_request_with_token()
|
||||
req.META['HTTP_HOST'] = 'www.example.com'
|
||||
req.META['HTTP_ORIGIN'] = 'null'
|
||||
mw = CsrfViewMiddleware(post_form_view)
|
||||
self.assertIs(mw._origin_verified(req), False)
|
||||
with self.assertLogs('django.security.csrf', 'WARNING') as cm:
|
||||
response = mw.process_view(req, post_form_view, (), {})
|
||||
self.assertEqual(response.status_code, 403)
|
||||
msg = REASON_BAD_ORIGIN % req.META['HTTP_ORIGIN']
|
||||
self.assertEqual(cm.records[0].getMessage(), 'Forbidden (%s): ' % msg)
|
||||
|
||||
@override_settings(ALLOWED_HOSTS=['www.example.com'])
|
||||
def test_bad_origin_bad_protocol(self):
|
||||
"""A request with an origin with wrong protocol is rejected."""
|
||||
req = self._get_POST_request_with_token()
|
||||
req._is_secure_override = True
|
||||
req.META['HTTP_HOST'] = 'www.example.com'
|
||||
req.META['HTTP_ORIGIN'] = 'http://example.com'
|
||||
mw = CsrfViewMiddleware(post_form_view)
|
||||
self.assertIs(mw._origin_verified(req), False)
|
||||
with self.assertLogs('django.security.csrf', 'WARNING') as cm:
|
||||
response = mw.process_view(req, post_form_view, (), {})
|
||||
self.assertEqual(response.status_code, 403)
|
||||
msg = REASON_BAD_ORIGIN % req.META['HTTP_ORIGIN']
|
||||
self.assertEqual(cm.records[0].getMessage(), 'Forbidden (%s): ' % msg)
|
||||
|
||||
@override_settings(
|
||||
ALLOWED_HOSTS=['www.example.com'],
|
||||
CSRF_TRUSTED_ORIGINS=[
|
||||
'http://no-match.com',
|
||||
'https://*.example.com',
|
||||
'http://*.no-match.com',
|
||||
'http://*.no-match-2.com',
|
||||
],
|
||||
)
|
||||
def test_bad_origin_csrf_trusted_origin_bad_protocol(self):
|
||||
"""
|
||||
A request with an origin with the wrong protocol compared to
|
||||
CSRF_TRUSTED_ORIGINS is rejected.
|
||||
"""
|
||||
req = self._get_POST_request_with_token()
|
||||
req._is_secure_override = True
|
||||
req.META['HTTP_HOST'] = 'www.example.com'
|
||||
req.META['HTTP_ORIGIN'] = 'http://foo.example.com'
|
||||
mw = CsrfViewMiddleware(post_form_view)
|
||||
self.assertIs(mw._origin_verified(req), False)
|
||||
with self.assertLogs('django.security.csrf', 'WARNING') as cm:
|
||||
response = mw.process_view(req, post_form_view, (), {})
|
||||
self.assertEqual(response.status_code, 403)
|
||||
msg = REASON_BAD_ORIGIN % req.META['HTTP_ORIGIN']
|
||||
self.assertEqual(cm.records[0].getMessage(), 'Forbidden (%s): ' % msg)
|
||||
self.assertEqual(mw.allowed_origins_exact, {'http://no-match.com'})
|
||||
self.assertEqual(mw.allowed_origin_subdomains, {
|
||||
'https': ['.example.com'],
|
||||
'http': ['.no-match.com', '.no-match-2.com'],
|
||||
})
|
||||
|
||||
@override_settings(ALLOWED_HOSTS=['www.example.com'])
|
||||
def test_bad_origin_cannot_be_parsed(self):
|
||||
"""
|
||||
A POST request with an origin that can't be parsed by urlparse() is
|
||||
rejected.
|
||||
"""
|
||||
req = self._get_POST_request_with_token()
|
||||
req.META['HTTP_HOST'] = 'www.example.com'
|
||||
req.META['HTTP_ORIGIN'] = 'https://['
|
||||
mw = CsrfViewMiddleware(post_form_view)
|
||||
self.assertIs(mw._origin_verified(req), False)
|
||||
with self.assertLogs('django.security.csrf', 'WARNING') as cm:
|
||||
response = mw.process_view(req, post_form_view, (), {})
|
||||
self.assertEqual(response.status_code, 403)
|
||||
msg = REASON_BAD_ORIGIN % req.META['HTTP_ORIGIN']
|
||||
self.assertEqual(cm.records[0].getMessage(), 'Forbidden (%s): ' % msg)
|
||||
|
||||
@override_settings(ALLOWED_HOSTS=['www.example.com'])
|
||||
def test_good_origin_insecure(self):
|
||||
"""A POST HTTP request with a good origin is accepted."""
|
||||
req = self._get_POST_request_with_token()
|
||||
req.META['HTTP_HOST'] = 'www.example.com'
|
||||
req.META['HTTP_ORIGIN'] = 'http://www.example.com'
|
||||
mw = CsrfViewMiddleware(post_form_view)
|
||||
self.assertIs(mw._origin_verified(req), True)
|
||||
response = mw.process_view(req, post_form_view, (), {})
|
||||
self.assertIsNone(response)
|
||||
|
||||
@override_settings(ALLOWED_HOSTS=['www.example.com'])
|
||||
def test_good_origin_secure(self):
|
||||
"""A POST HTTPS request with a good origin is accepted."""
|
||||
req = self._get_POST_request_with_token()
|
||||
req._is_secure_override = True
|
||||
req.META['HTTP_HOST'] = 'www.example.com'
|
||||
req.META['HTTP_ORIGIN'] = 'https://www.example.com'
|
||||
mw = CsrfViewMiddleware(post_form_view)
|
||||
self.assertIs(mw._origin_verified(req), True)
|
||||
response = mw.process_view(req, post_form_view, (), {})
|
||||
self.assertIsNone(response)
|
||||
|
||||
@override_settings(ALLOWED_HOSTS=['www.example.com'], CSRF_TRUSTED_ORIGINS=['https://dashboard.example.com'])
|
||||
def test_good_origin_csrf_trusted_origin_allowed(self):
|
||||
"""
|
||||
A POST request with an origin 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_ORIGIN'] = 'https://dashboard.example.com'
|
||||
mw = CsrfViewMiddleware(post_form_view)
|
||||
self.assertIs(mw._origin_verified(req), True)
|
||||
resp = mw.process_view(req, post_form_view, (), {})
|
||||
self.assertIsNone(resp)
|
||||
self.assertEqual(mw.allowed_origins_exact, {'https://dashboard.example.com'})
|
||||
self.assertEqual(mw.allowed_origin_subdomains, {})
|
||||
|
||||
@override_settings(ALLOWED_HOSTS=['www.example.com'], CSRF_TRUSTED_ORIGINS=['https://*.example.com'])
|
||||
def test_good_origin_wildcard_csrf_trusted_origin_allowed(self):
|
||||
"""
|
||||
A POST request with an origin that matches a CSRF_TRUSTED_ORIGINS
|
||||
wildcard is accepted.
|
||||
"""
|
||||
req = self._get_POST_request_with_token()
|
||||
req._is_secure_override = True
|
||||
req.META['HTTP_HOST'] = 'www.example.com'
|
||||
req.META['HTTP_ORIGIN'] = 'https://foo.example.com'
|
||||
mw = CsrfViewMiddleware(post_form_view)
|
||||
self.assertIs(mw._origin_verified(req), True)
|
||||
response = mw.process_view(req, post_form_view, (), {})
|
||||
self.assertIsNone(response)
|
||||
self.assertEqual(mw.allowed_origins_exact, set())
|
||||
self.assertEqual(mw.allowed_origin_subdomains, {'https': ['.example.com']})
|
||||
|
||||
|
||||
class CsrfViewMiddlewareTests(CsrfViewMiddlewareTestMixin, SimpleTestCase):
|
||||
|
||||
|
||||
Reference in New Issue
Block a user