[1.3.x] Fixed a security issue in http redirects. Disclosure and new release forthcoming.

Backport of 4129201c3e from master.
This commit is contained in:
Florian Apolloner 2012-07-30 22:03:46 +02:00
parent b2eb4787a0
commit 4dea4883e6
2 changed files with 31 additions and 13 deletions

View File

@ -4,7 +4,7 @@ import re
import time import time
from pprint import pformat from pprint import pformat
from urllib import urlencode, quote from urllib import urlencode, quote
from urlparse import urljoin from urlparse import urljoin, urlparse
try: try:
from cStringIO import StringIO from cStringIO import StringIO
except ImportError: except ImportError:
@ -117,6 +117,7 @@ class CompatCookie(SimpleCookie):
warnings.warn("CompatCookie is deprecated, use django.http.SimpleCookie instead.", warnings.warn("CompatCookie is deprecated, use django.http.SimpleCookie instead.",
PendingDeprecationWarning) PendingDeprecationWarning)
from django.core.exceptions import SuspiciousOperation
from django.utils.datastructures import MultiValueDict, ImmutableList from django.utils.datastructures import MultiValueDict, ImmutableList
from django.utils.encoding import smart_str, iri_to_uri, force_unicode from django.utils.encoding import smart_str, iri_to_uri, force_unicode
from django.utils.http import cookie_date from django.utils.http import cookie_date
@ -635,20 +636,22 @@ class HttpResponse(object):
raise Exception("This %s instance cannot tell its position" % self.__class__) raise Exception("This %s instance cannot tell its position" % self.__class__)
return sum([len(chunk) for chunk in self._container]) return sum([len(chunk) for chunk in self._container])
class HttpResponseRedirect(HttpResponse): class HttpResponseRedirectBase(HttpResponse):
allowed_schemes = ['http', 'https', 'ftp']
def __init__(self, redirect_to):
super(HttpResponseRedirectBase, self).__init__()
parsed = urlparse(redirect_to)
if parsed.scheme and parsed.scheme not in self.allowed_schemes:
raise SuspiciousOperation("Unsafe redirect to URL with scheme '%s'" % parsed.scheme)
self['Location'] = iri_to_uri(redirect_to)
class HttpResponseRedirect(HttpResponseRedirectBase):
status_code = 302 status_code = 302
def __init__(self, redirect_to): class HttpResponsePermanentRedirect(HttpResponseRedirectBase):
super(HttpResponseRedirect, self).__init__()
self['Location'] = iri_to_uri(redirect_to)
class HttpResponsePermanentRedirect(HttpResponse):
status_code = 301 status_code = 301
def __init__(self, redirect_to):
super(HttpResponsePermanentRedirect, self).__init__()
self['Location'] = iri_to_uri(redirect_to)
class HttpResponseNotModified(HttpResponse): class HttpResponseNotModified(HttpResponse):
status_code = 304 status_code = 304

View File

@ -1,8 +1,11 @@
import copy import copy
import pickle import pickle
from django.http import (QueryDict, HttpResponse, SimpleCookie, BadHeaderError, from django.core.exceptions import SuspiciousOperation
parse_cookie) from django.http import (QueryDict, HttpResponse, HttpResponseRedirect,
HttpResponsePermanentRedirect,
SimpleCookie, BadHeaderError,
parse_cookie)
from django.utils import unittest from django.utils import unittest
class QueryDictTests(unittest.TestCase): class QueryDictTests(unittest.TestCase):
@ -243,6 +246,18 @@ class HttpResponseTests(unittest.TestCase):
self.assertRaises(BadHeaderError, r.__setitem__, 'test\rstr', 'test') self.assertRaises(BadHeaderError, r.__setitem__, 'test\rstr', 'test')
self.assertRaises(BadHeaderError, r.__setitem__, 'test\nstr', 'test') self.assertRaises(BadHeaderError, r.__setitem__, 'test\nstr', 'test')
def test_unsafe_redirects(self):
bad_urls = [
'data:text/html,<script>window.alert("xss")</script>',
'mailto:test@example.com',
'file:///etc/passwd',
]
for url in bad_urls:
self.assertRaises(SuspiciousOperation,
HttpResponseRedirect, url)
self.assertRaises(SuspiciousOperation,
HttpResponsePermanentRedirect, url)
class CookieTests(unittest.TestCase): class CookieTests(unittest.TestCase):
def test_encode(self): def test_encode(self):
""" """