Skip to content

Commit e346850

Browse files
committed
[1.4.x] Fixed a security issue in http redirects. Disclosure and new release forthcoming.
Backport of 4129201 from master.
1 parent c14f325 commit e346850

File tree

2 files changed

+29
-12
lines changed

2 files changed

+29
-12
lines changed

django/http/__init__.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
from pprint import pformat
1111
from urllib import urlencode, quote
12-
from urlparse import urljoin
12+
from urlparse import urljoin, urlparse
1313
try:
1414
from cStringIO import StringIO
1515
except ImportError:
@@ -114,7 +114,7 @@ def __init__(self, *args, **kwargs):
114114

115115
from django.conf import settings
116116
from django.core import signing
117-
from django.core.exceptions import ImproperlyConfigured
117+
from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation
118118
from django.core.files import uploadhandler
119119
from django.http.multipartparser import MultiPartParser
120120
from django.http.utils import *
@@ -731,19 +731,21 @@ def tell(self):
731731
raise Exception("This %s instance cannot tell its position" % self.__class__)
732732
return sum([len(str(chunk)) for chunk in self._container])
733733

734-
class HttpResponseRedirect(HttpResponse):
735-
status_code = 302
734+
class HttpResponseRedirectBase(HttpResponse):
735+
allowed_schemes = ['http', 'https', 'ftp']
736736

737737
def __init__(self, redirect_to):
738-
super(HttpResponseRedirect, self).__init__()
738+
super(HttpResponseRedirectBase, self).__init__()
739+
parsed = urlparse(redirect_to)
740+
if parsed.scheme and parsed.scheme not in self.allowed_schemes:
741+
raise SuspiciousOperation("Unsafe redirect to URL with scheme '%s'" % parsed.scheme)
739742
self['Location'] = iri_to_uri(redirect_to)
740743

741-
class HttpResponsePermanentRedirect(HttpResponse):
742-
status_code = 301
744+
class HttpResponseRedirect(HttpResponseRedirectBase):
745+
status_code = 302
743746

744-
def __init__(self, redirect_to):
745-
super(HttpResponsePermanentRedirect, self).__init__()
746-
self['Location'] = iri_to_uri(redirect_to)
747+
class HttpResponsePermanentRedirect(HttpResponseRedirectBase):
748+
status_code = 301
747749

748750
class HttpResponseNotModified(HttpResponse):
749751
status_code = 304

tests/regressiontests/httpwrappers/tests.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import copy
22
import pickle
33

4-
from django.http import (QueryDict, HttpResponse, SimpleCookie, BadHeaderError,
5-
parse_cookie)
4+
from django.core.exceptions import SuspiciousOperation
5+
from django.http import (QueryDict, HttpResponse, HttpResponseRedirect,
6+
HttpResponsePermanentRedirect,
7+
SimpleCookie, BadHeaderError,
8+
parse_cookie)
69
from django.utils import unittest
710

811

@@ -296,6 +299,18 @@ def test_iter_content(self):
296299
self.assertRaises(UnicodeEncodeError,
297300
getattr, r, 'content')
298301

302+
def test_unsafe_redirect(self):
303+
bad_urls = [
304+
'data:text/html,<script>window.alert("xss")</script>',
305+
'mailto:test@example.com',
306+
'file:///etc/passwd',
307+
]
308+
for url in bad_urls:
309+
self.assertRaises(SuspiciousOperation,
310+
HttpResponseRedirect, url)
311+
self.assertRaises(SuspiciousOperation,
312+
HttpResponsePermanentRedirect, url)
313+
299314
class CookieTests(unittest.TestCase):
300315
def test_encode(self):
301316
"""

0 commit comments

Comments
 (0)