Commit 4129201c authored by Florian Apolloner's avatar Florian Apolloner
Browse files

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

parent b1d46346
Loading
Loading
Loading
Loading
+13 −11
Original line number Diff line number Diff line
@@ -11,10 +11,10 @@ import warnings
from io import BytesIO
from pprint import pformat
try:
    from urllib.parse import quote, parse_qsl, urlencode, urljoin
    from urllib.parse import quote, parse_qsl, urlencode, urljoin, urlparse
except ImportError:     # Python 2
    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
# 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.core import signing
from django.core.exceptions import ImproperlyConfigured
from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation
from django.core.files import uploadhandler
from django.http.multipartparser import MultiPartParser
from django.http.utils import *
@@ -689,19 +689,21 @@ class HttpResponse(object):
            raise Exception("This %s instance cannot tell its position" % self.__class__)
        return sum([len(chunk) for chunk in self])

class HttpResponseRedirect(HttpResponse):
    status_code = 302
class HttpResponseRedirectBase(HttpResponse):
    allowed_schemes = ['http', 'https', 'ftp']

    def __init__(self, redirect_to):
        super(HttpResponseRedirect, self).__init__()
        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 HttpResponsePermanentRedirect(HttpResponse):
    status_code = 301
class HttpResponseRedirect(HttpResponseRedirectBase):
    status_code = 302

    def __init__(self, redirect_to):
        super(HttpResponsePermanentRedirect, self).__init__()
        self['Location'] = iri_to_uri(redirect_to)
class HttpResponsePermanentRedirect(HttpResponseRedirectBase):
    status_code = 301

class HttpResponseNotModified(HttpResponse):
    status_code = 304
+17 −2
Original line number Diff line number Diff line
@@ -4,7 +4,10 @@ from __future__ import unicode_literals
import copy
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)
from django.utils import unittest

@@ -309,6 +312,18 @@ class HttpResponseTests(unittest.TestCase):
        r = HttpResponse(['abc'])
        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):
    def test_encode(self):