Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed a security issue in http redirects. Disclosure and new release …

…forthcoming.
  • Loading branch information...
commit 4129201c3e0fa057c198bdefcb34686a23b4a93c 1 parent b1d4634
Florian Apolloner authored
24  django/http/__init__.py
@@ -11,10 +11,10 @@
11 11
 from io import BytesIO
12 12
 from pprint import pformat
13 13
 try:
14  
-    from urllib.parse import quote, parse_qsl, urlencode, urljoin
  14
+    from urllib.parse import quote, parse_qsl, urlencode, urljoin, urlparse
15 15
 except ImportError:     # Python 2
16 16
     from urllib import quote, urlencode
17  
-    from urlparse import parse_qsl, urljoin
  17
+    from urlparse import parse_qsl, urljoin, urlparse
18 18
 
19 19
 from django.utils.six.moves import http_cookies
20 20
 # Some versions of Python 2.7 and later won't need this encoding bug fix:
@@ -80,7 +80,7 @@ def _BaseCookie__set(self, key, real_value, coded_value):
80 80
 
81 81
 from django.conf import settings
82 82
 from django.core import signing
83  
-from django.core.exceptions import ImproperlyConfigured
  83
+from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation
84 84
 from django.core.files import uploadhandler
85 85
 from django.http.multipartparser import MultiPartParser
86 86
 from django.http.utils import *
@@ -689,20 +689,22 @@ def tell(self):
689 689
             raise Exception("This %s instance cannot tell its position" % self.__class__)
690 690
         return sum([len(chunk) for chunk in self])
691 691
 
692  
-class HttpResponseRedirect(HttpResponse):
693  
-    status_code = 302
  692
+class HttpResponseRedirectBase(HttpResponse):
  693
+    allowed_schemes = ['http', 'https', 'ftp']
694 694
 
695 695
     def __init__(self, redirect_to):
696  
-        super(HttpResponseRedirect, self).__init__()
  696
+        parsed = urlparse(redirect_to)
  697
+        if parsed.scheme and parsed.scheme not in self.allowed_schemes:
  698
+            raise SuspiciousOperation("Unsafe redirect to URL with protocol '%s'" % parsed.scheme)
  699
+        super(HttpResponseRedirectBase, self).__init__()
697 700
         self['Location'] = iri_to_uri(redirect_to)
  701
+    
  702
+class HttpResponseRedirect(HttpResponseRedirectBase):
  703
+    status_code = 302
698 704
 
699  
-class HttpResponsePermanentRedirect(HttpResponse):
  705
+class HttpResponsePermanentRedirect(HttpResponseRedirectBase):
700 706
     status_code = 301
701 707
 
702  
-    def __init__(self, redirect_to):
703  
-        super(HttpResponsePermanentRedirect, self).__init__()
704  
-        self['Location'] = iri_to_uri(redirect_to)
705  
-
706 708
 class HttpResponseNotModified(HttpResponse):
707 709
     status_code = 304
708 710
 
19  tests/regressiontests/httpwrappers/tests.py
@@ -4,8 +4,11 @@
4 4
 import copy
5 5
 import pickle
6 6
 
7  
-from django.http import (QueryDict, HttpResponse, SimpleCookie, BadHeaderError,
8  
-        parse_cookie)
  7
+from django.core.exceptions import SuspiciousOperation
  8
+from django.http import (QueryDict, HttpResponse, HttpResponseRedirect,
  9
+                         HttpResponsePermanentRedirect,
  10
+                         SimpleCookie, BadHeaderError,
  11
+                         parse_cookie)
9 12
 from django.utils import unittest
10 13
 
11 14
 
@@ -309,6 +312,18 @@ def test_file_interface(self):
309 312
         r = HttpResponse(['abc'])
310 313
         self.assertRaises(Exception, r.write, 'def')
311 314
 
  315
+    def test_unsafe_redirect(self):
  316
+        bad_urls = [
  317
+            'data:text/html,<script>window.alert("xss")</script>',
  318
+            'mailto:test@example.com',
  319
+            'file:///etc/passwd',
  320
+        ]
  321
+        for url in bad_urls:
  322
+            self.assertRaises(SuspiciousOperation,
  323
+                              HttpResponseRedirect, url)
  324
+            self.assertRaises(SuspiciousOperation,
  325
+                              HttpResponsePermanentRedirect, url)
  326
+
312 327
 
313 328
 class CookieTests(unittest.TestCase):
314 329
     def test_encode(self):

0 notes on commit 4129201

Please sign in to comment.
Something went wrong with that request. Please try again.