diff options
author | Matt Robenolt <matt@ydekproductions.com> | 2015-03-17 02:52:55 -0700 |
---|---|---|
committer | Tim Graham <timograham@gmail.com> | 2015-09-16 12:21:50 -0400 |
commit | b0c56b895fd2694d7f5d4595bdbbc41916607f45 (patch) | |
tree | 4ac4ef6e9e3cc89263f99ef76321ca88b2968a1c /tests | |
parent | 535809e12161d28dacaf5161436fc05a9bb064aa (diff) | |
download | django-b0c56b895fd2694d7f5d4595bdbbc41916607f45.tar.gz |
Fixed #24496 -- Added CSRF Referer checking against CSRF_COOKIE_DOMAIN.
Thanks Seth Gottlieb for help with the documentation and
Carl Meyer and Joshua Kehn for reviews.
Diffstat (limited to 'tests')
-rw-r--r-- | tests/csrf_tests/tests.py | 105 | ||||
-rw-r--r-- | tests/utils_tests/test_http.py | 44 |
2 files changed, 111 insertions, 38 deletions
diff --git a/tests/csrf_tests/tests.py b/tests/csrf_tests/tests.py index 382242d6a4..6c6f49d2b8 100644 --- a/tests/csrf_tests/tests.py +++ b/tests/csrf_tests/tests.py @@ -295,7 +295,7 @@ class CsrfViewMiddlewareTest(SimpleTestCase): csrf_cookie = resp2.cookies[settings.CSRF_COOKIE_NAME] self._check_token_present(resp, csrf_id=csrf_cookie.value) - @override_settings(ALLOWED_HOSTS=['www.example.com']) + @override_settings(DEBUG=True) def test_https_bad_referer(self): """ Test that a POST HTTPS request with a bad referer is rejected @@ -304,27 +304,50 @@ class CsrfViewMiddlewareTest(SimpleTestCase): req._is_secure_override = True req.META['HTTP_HOST'] = 'www.example.com' req.META['HTTP_REFERER'] = 'https://www.evil.org/somepage' - req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) - self.assertIsNotNone(req2) - self.assertEqual(403, req2.status_code) - - @override_settings(ALLOWED_HOSTS=['www.example.com']) + req.META['SERVER_PORT'] = '443' + response = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + self.assertContains( + response, + 'Referer checking failed - https://www.evil.org/somepage does not ' + 'match any trusted origins.', + status_code=403, + ) + + @override_settings(DEBUG=True) def test_https_malformed_referer(self): """ A POST HTTPS request with a bad referer is rejected. """ + malformed_referer_msg = 'Referer checking failed - Referer is malformed.' req = self._get_POST_request_with_token() req._is_secure_override = True - req.META['HTTP_HOST'] = 'www.example.com' req.META['HTTP_REFERER'] = 'http://http://www.example.com/' - req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) - self.assertIsNotNone(req2) - self.assertEqual(403, req2.status_code) + response = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + self.assertContains( + response, + 'Referer checking failed - Referer is insecure while host is secure.', + status_code=403, + ) + # Empty + req.META['HTTP_REFERER'] = '' + response = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + self.assertContains(response, malformed_referer_msg, status_code=403) # Non-ASCII req.META['HTTP_REFERER'] = b'\xd8B\xf6I\xdf' - req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) - self.assertIsNotNone(req2) - self.assertEqual(403, req2.status_code) + response = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + self.assertContains(response, malformed_referer_msg, status_code=403) + # missing scheme + # >>> urlparse('//example.com/') + # ParseResult(scheme='', netloc='example.com', path='/', params='', query='', fragment='') + req.META['HTTP_REFERER'] = '//example.com/' + response = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + self.assertContains(response, malformed_referer_msg, status_code=403) + # missing netloc + # >>> urlparse('https://') + # ParseResult(scheme='https', netloc='', path='', params='', query='', fragment='') + req.META['HTTP_REFERER'] = 'https://' + response = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + self.assertContains(response, malformed_referer_msg, status_code=403) @override_settings(ALLOWED_HOSTS=['www.example.com']) def test_https_good_referer(self): @@ -365,6 +388,62 @@ class CsrfViewMiddlewareTest(SimpleTestCase): req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) self.assertIsNone(req2) + @override_settings(ALLOWED_HOSTS=['www.example.com'], CSRF_TRUSTED_ORIGINS=['.example.com']) + def test_https_csrf_wildcard_trusted_origin_allowed(self): + """ + A POST HTTPS request with a referer that matches a CSRF_TRUSTED_ORIGINS + wilcard 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' + response = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + self.assertIsNone(response) + + @override_settings(ALLOWED_HOSTS=['www.example.com'], CSRF_COOKIE_DOMAIN='.example.com') + def test_https_good_referer_matches_cookie_domain(self): + """ + A POST HTTPS request with a good referer should be accepted from a + subdomain that's allowed by CSRF_COOKIE_DOMAIN. + """ + req = self._get_POST_request_with_token() + req._is_secure_override = True + req.META['HTTP_REFERER'] = 'https://foo.example.com/' + req.META['SERVER_PORT'] = '443' + response = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + self.assertIsNone(response) + + @override_settings(ALLOWED_HOSTS=['www.example.com'], CSRF_COOKIE_DOMAIN='.example.com') + def test_https_good_referer_matches_cookie_domain_with_different_port(self): + """ + A POST HTTPS request with a good referer should be accepted from a + subdomain that's allowed by CSRF_COOKIE_DOMAIN and a non-443 port. + """ + req = self._get_POST_request_with_token() + req._is_secure_override = True + req.META['HTTP_HOST'] = 'www.example.com' + req.META['HTTP_REFERER'] = 'https://foo.example.com:4443/' + req.META['SERVER_PORT'] = '4443' + response = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + self.assertIsNone(response) + + @override_settings(CSRF_COOKIE_DOMAIN='.example.com', DEBUG=True) + def test_https_reject_insecure_referer(self): + """ + A POST HTTPS request from an insecure referer should be rejected. + """ + req = self._get_POST_request_with_token() + req._is_secure_override = True + req.META['HTTP_REFERER'] = 'http://example.com/' + req.META['SERVER_PORT'] = '443' + response = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + self.assertContains( + response, + 'Referer checking failed - Referer is insecure while host is secure.', + status_code=403, + ) + def test_ensures_csrf_cookie_no_middleware(self): """ The ensure_csrf_cookie() decorator works without middleware. diff --git a/tests/utils_tests/test_http.py b/tests/utils_tests/test_http.py index 74c6905294..baa126d423 100644 --- a/tests/utils_tests/test_http.py +++ b/tests/utils_tests/test_http.py @@ -10,31 +10,6 @@ from django.utils.datastructures import MultiValueDict class TestUtilsHttp(unittest.TestCase): - def test_same_origin_true(self): - # Identical - self.assertTrue(http.same_origin('http://foo.com/', 'http://foo.com/')) - # One with trailing slash - see #15617 - self.assertTrue(http.same_origin('http://foo.com', 'http://foo.com/')) - self.assertTrue(http.same_origin('http://foo.com/', 'http://foo.com')) - # With port - self.assertTrue(http.same_origin('https://foo.com:8000', 'https://foo.com:8000/')) - # No port given but according to RFC6454 still the same origin - self.assertTrue(http.same_origin('http://foo.com', 'http://foo.com:80/')) - self.assertTrue(http.same_origin('https://foo.com', 'https://foo.com:443/')) - - def test_same_origin_false(self): - # Different scheme - self.assertFalse(http.same_origin('http://foo.com', 'https://foo.com')) - # Different host - self.assertFalse(http.same_origin('http://foo.com', 'http://goo.com')) - # Different host again - self.assertFalse(http.same_origin('http://foo.com', 'http://foo.com.evil.com')) - # Different port - self.assertFalse(http.same_origin('http://foo.com:8000', 'http://foo.com:8001')) - # No port given - self.assertFalse(http.same_origin('http://foo.com', 'http://foo.com:8000/')) - self.assertFalse(http.same_origin('https://foo.com', 'https://foo.com:8000/')) - def test_urlencode(self): # 2-tuples (the norm) result = http.urlencode((('a', 1), ('b', 2), ('c', 3))) @@ -157,6 +132,25 @@ class TestUtilsHttp(unittest.TestCase): http.urlunquote_plus('Paris+&+Orl%C3%A9ans'), 'Paris & Orl\xe9ans') + def test_is_same_domain_good(self): + for pair in ( + ('example.com', 'example.com'), + ('example.com', '.example.com'), + ('foo.example.com', '.example.com'), + ('example.com:8888', 'example.com:8888'), + ('example.com:8888', '.example.com:8888'), + ('foo.example.com:8888', '.example.com:8888'), + ): + self.assertTrue(http.is_same_domain(*pair)) + + def test_is_same_domain_bad(self): + for pair in ( + ('example2.com', 'example.com'), + ('foo.example.com', 'example.com'), + ('example.com:9999', 'example.com:8888'), + ): + self.assertFalse(http.is_same_domain(*pair)) + class ETagProcessingTests(unittest.TestCase): def test_parsing(self): |