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

This commit is contained in:
Florian Apolloner 2012-07-30 22:01:50 +02:00
parent b1d4634686
commit 4129201c3e
2 changed files with 32 additions and 15 deletions

View File

@ -11,10 +11,10 @@ import warnings
from io import BytesIO from io import BytesIO
from pprint import pformat from pprint import pformat
try: try:
from urllib.parse import quote, parse_qsl, urlencode, urljoin from urllib.parse import quote, parse_qsl, urlencode, urljoin, urlparse
except ImportError: # Python 2 except ImportError: # Python 2
from urllib import quote, urlencode from urllib import quote, urlencode
from urlparse import parse_qsl, urljoin from urlparse import parse_qsl, urljoin, urlparse
from django.utils.six.moves import http_cookies from django.utils.six.moves import http_cookies
# Some versions of Python 2.7 and later won't need this encoding bug fix: # Some versions of Python 2.7 and later won't need this encoding bug fix:
@ -80,7 +80,7 @@ else:
from django.conf import settings from django.conf import settings
from django.core import signing from django.core import signing
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation
from django.core.files import uploadhandler from django.core.files import uploadhandler
from django.http.multipartparser import MultiPartParser from django.http.multipartparser import MultiPartParser
from django.http.utils import * from django.http.utils import *
@ -689,20 +689,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]) return sum([len(chunk) for chunk in self])
class HttpResponseRedirect(HttpResponse): class HttpResponseRedirectBase(HttpResponse):
allowed_schemes = ['http', 'https', 'ftp']
def __init__(self, redirect_to):
parsed = urlparse(redirect_to)
if parsed.scheme and parsed.scheme not in self.allowed_schemes:
raise SuspiciousOperation("Unsafe redirect to URL with protocol '%s'" % parsed.scheme)
super(HttpResponseRedirectBase, self).__init__()
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

@ -4,7 +4,10 @@ from __future__ import unicode_literals
import copy import copy
import pickle import pickle
from django.http import (QueryDict, HttpResponse, SimpleCookie, BadHeaderError, from django.core.exceptions import SuspiciousOperation
from django.http import (QueryDict, HttpResponse, HttpResponseRedirect,
HttpResponsePermanentRedirect,
SimpleCookie, BadHeaderError,
parse_cookie) parse_cookie)
from django.utils import unittest from django.utils import unittest
@ -309,6 +312,18 @@ class HttpResponseTests(unittest.TestCase):
r = HttpResponse(['abc']) r = HttpResponse(['abc'])
self.assertRaises(Exception, r.write, 'def') self.assertRaises(Exception, r.write, 'def')
def test_unsafe_redirect(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):