VYPR
Critical severityNVD Advisory· Published Jul 31, 2012· Updated Apr 16, 2026

CVE-2012-3442

CVE-2012-3442

Description

The (1) django.http.HttpResponseRedirect and (2) django.http.HttpResponsePermanentRedirect classes in Django before 1.3.2 and 1.4.x before 1.4.1 do not validate the scheme of a redirect target, which might allow remote attackers to conduct cross-site scripting (XSS) attacks via a data: URL.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
DjangoPyPI
< 1.3.21.3.2
DjangoPyPI
>= 1.4, < 1.4.11.4.1

Affected products

2
  • cpe:2.3:a:djangoproject:django:*:*:*:*:*:*:*:*+ 1 more
    • cpe:2.3:a:djangoproject:django:*:*:*:*:*:*:*:*range: <1.3.2
    • cpe:2.3:a:djangoproject:django:1.4:*:*:*:*:*:*:*

Patches

2
4dea4883e6c5

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

https://github.com/django/djangoFlorian ApollonerJul 30, 2012via ghsa
2 files changed · +29 11
  • django/http/__init__.py+12 9 modified
    @@ -4,7 +4,7 @@
     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 @@ def __init__(self, *args, **kwargs):
             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 @@ def tell(self):
                 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
    
  • tests/regressiontests/httpwrappers/tests.py+17 2 modified
    @@ -1,8 +1,11 @@
     import copy
     import pickle
     
    -from django.http import (QueryDict, HttpResponse, SimpleCookie, BadHeaderError,
    -        parse_cookie)
    +from django.core.exceptions import SuspiciousOperation
    +from django.http import (QueryDict, HttpResponse, HttpResponseRedirect,
    +                         HttpResponsePermanentRedirect,
    +                         SimpleCookie, BadHeaderError,
    +                         parse_cookie)
     from django.utils import unittest
     
     class QueryDictTests(unittest.TestCase):
    @@ -243,6 +246,18 @@ def test_newlines_in_headers(self):
             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):
             """
    
e34685034b60

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

https://github.com/django/djangoFlorian ApollonerJul 30, 2012via ghsa
2 files changed · +29 12
  • django/http/__init__.py+12 10 modified
    @@ -9,7 +9,7 @@
     
     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:
    @@ -114,7 +114,7 @@ def __init__(self, *args, **kwargs):
     
     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 *
    @@ -731,19 +731,21 @@ def tell(self):
                 raise Exception("This %s instance cannot tell its position" % self.__class__)
             return sum([len(str(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
    
  • tests/regressiontests/httpwrappers/tests.py+17 2 modified
    @@ -1,8 +1,11 @@
     import copy
     import pickle
     
    -from django.http import (QueryDict, HttpResponse, SimpleCookie, BadHeaderError,
    -        parse_cookie)
    +from django.core.exceptions import SuspiciousOperation
    +from django.http import (QueryDict, HttpResponse, HttpResponseRedirect,
    +                         HttpResponsePermanentRedirect,
    +                         SimpleCookie, BadHeaderError,
    +                         parse_cookie)
     from django.utils import unittest
     
     
    @@ -296,6 +299,18 @@ def test_iter_content(self):
             self.assertRaises(UnicodeEncodeError,
                               getattr, r, 'content')
     
    +    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):
             """
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

12

News mentions

0

No linked articles in our index yet.