|
5 | 5 |
|
6 | 6 | from urllib.parse import urlparse |
7 | 7 |
|
| 8 | +from django.utils.http import url_has_allowed_host_and_scheme |
| 9 | + |
8 | 10 | from .base import TestCase |
9 | 11 |
|
10 | 12 |
|
@@ -43,3 +45,23 @@ def test_follow_open_redirect_prevention(self): |
43 | 45 | parsed = urlparse(redirect_url) |
44 | 46 | self.assertFalse(bool(parsed.netloc), |
45 | 47 | 'Open redirect to external domain detected: %s' % redirect_url) |
| 48 | + |
| 49 | + def test_follow_open_redirect_backslash_prevention(self): |
| 50 | + """Test that backslash-prefixed URLs cannot cause open redirects. |
| 51 | +
|
| 52 | + Some browsers interpret /\\evil.com as //evil.com (a protocol-relative |
| 53 | + URL pointing to evil.com). The follow() view must reject such URLs. |
| 54 | + """ |
| 55 | + shorten_url = reverse('shorten', kwargs={'path': '\\evil.com'}) |
| 56 | + response = self.client.get(shorten_url) |
| 57 | + self.assertEqual(response.status_code, 200) |
| 58 | + short_path = response.content.decode('utf-8') |
| 59 | + |
| 60 | + follow_response = self.client.get(short_path) |
| 61 | + self.assertEqual(follow_response.status_code, 301) |
| 62 | + redirect_url = follow_response['Location'] |
| 63 | + # The redirect must be a safe internal URL |
| 64 | + self.assertTrue( |
| 65 | + url_has_allowed_host_and_scheme(url=redirect_url, allowed_hosts={'testserver'}), |
| 66 | + 'Unsafe redirect detected: %s' % redirect_url, |
| 67 | + ) |
0 commit comments