Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 4dea488

Browse files
committed
[1.3.x] Fixed a security issue in http redirects. Disclosure and new release forthcoming.
Backport of 4129201 from master.
1 parent b2eb478 commit 4dea488

File tree

2 files changed

+29
-11
lines changed

2 files changed

+29
-11
lines changed

django/http/__init__.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import time
55
from pprint import pformat
66
from urllib import urlencode, quote
7-
from urlparse import urljoin
7+
from urlparse import urljoin, urlparse
88
try:
99
from cStringIO import StringIO
1010
except ImportError:
@@ -117,6 +117,7 @@ def __init__(self, *args, **kwargs):
117117
warnings.warn("CompatCookie is deprecated, use django.http.SimpleCookie instead.",
118118
PendingDeprecationWarning)
119119

120+
from django.core.exceptions import SuspiciousOperation
120121
from django.utils.datastructures import MultiValueDict, ImmutableList
121122
from django.utils.encoding import smart_str, iri_to_uri, force_unicode
122123
from django.utils.http import cookie_date
@@ -635,19 +636,21 @@ def tell(self):
635636
raise Exception("This %s instance cannot tell its position" % self.__class__)
636637
return sum([len(chunk) for chunk in self._container])
637638

638-
class HttpResponseRedirect(HttpResponse):
639-
status_code = 302
639+
class HttpResponseRedirectBase(HttpResponse):
640+
allowed_schemes = ['http', 'https', 'ftp']
640641

641642
def __init__(self, redirect_to):
642-
super(HttpResponseRedirect, self).__init__()
643+
super(HttpResponseRedirectBase, self).__init__()
644+
parsed = urlparse(redirect_to)
645+
if parsed.scheme and parsed.scheme not in self.allowed_schemes:
646+
raise SuspiciousOperation("Unsafe redirect to URL with scheme '%s'" % parsed.scheme)
643647
self['Location'] = iri_to_uri(redirect_to)
644648

645-
class HttpResponsePermanentRedirect(HttpResponse):
646-
status_code = 301
649+
class HttpResponseRedirect(HttpResponseRedirectBase):
650+
status_code = 302
647651

648-
def __init__(self, redirect_to):
649-
super(HttpResponsePermanentRedirect, self).__init__()
650-
self['Location'] = iri_to_uri(redirect_to)
652+
class HttpResponsePermanentRedirect(HttpResponseRedirectBase):
653+
status_code = 301
651654

652655
class HttpResponseNotModified(HttpResponse):
653656
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
class QueryDictTests(unittest.TestCase):
@@ -243,6 +246,18 @@ def test_newlines_in_headers(self):
243246
self.assertRaises(BadHeaderError, r.__setitem__, 'test\rstr', 'test')
244247
self.assertRaises(BadHeaderError, r.__setitem__, 'test\nstr', 'test')
245248

249+
def test_unsafe_redirects(self):
250+
bad_urls = [
251+
'data:text/html,<script>window.alert("xss")</script>',
252+
253+
'file:///etc/passwd',
254+
]
255+
for url in bad_urls:
256+
self.assertRaises(SuspiciousOperation,
257+
HttpResponseRedirect, url)
258+
self.assertRaises(SuspiciousOperation,
259+
HttpResponsePermanentRedirect, url)
260+
246261
class CookieTests(unittest.TestCase):
247262
def test_encode(self):
248263
"""

0 commit comments

Comments
 (0)