1
0
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:
Tim Graham
2021-01-02 18:46:17 -05:00
committed by Mariusz Felisiak
parent dba44a7a7a
commit 2411b8b5eb
5 changed files with 238 additions and 13 deletions

View File

@@ -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):