Commit 4dea4883 authored by Florian Apolloner's avatar Florian Apolloner
Browse files

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

Backport of 4129201c from master.
parent b2eb4787
Loading
Loading
Loading
Loading
+12 −9
Original line number Diff line number Diff line
@@ -4,7 +4,7 @@ import re
import time
from pprint import pformat
from urllib import urlencode, quote
from urlparse import urljoin
from urlparse import urljoin, urlparse
try:
    from cStringIO import StringIO
except ImportError:
@@ -117,6 +117,7 @@ class CompatCookie(SimpleCookie):
        warnings.warn("CompatCookie is deprecated, use django.http.SimpleCookie instead.",
                      PendingDeprecationWarning)

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

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

    def __init__(self, redirect_to):
        super(HttpResponseRedirect, self).__init__()
        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 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
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

@@ -243,6 +246,18 @@ class HttpResponseTests(unittest.TestCase):
        self.assertRaises(BadHeaderError, r.__setitem__, 'test\rstr', '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):
    def test_encode(self):
        """