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.
| Package | Affected versions | Patched versions |
|---|---|---|
DjangoPyPI | < 1.3.2 | 1.3.2 |
DjangoPyPI | >= 1.4, < 1.4.1 | 1.4.1 |
Affected products
2cpe: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
24dea4883e6c5[1.3.x] Fixed a security issue in http redirects. Disclosure and new release forthcoming.
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.
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- www.djangoproject.com/weblog/2012/jul/30/security-releases-issued/nvdPatchVendor Advisory
- www.debian.org/security/2012/dsa-2529nvdThird Party AdvisoryWEB
- www.openwall.com/lists/oss-security/2012/07/31/1nvdMailing ListThird Party AdvisoryWEB
- www.openwall.com/lists/oss-security/2012/07/31/2nvdMailing ListThird Party AdvisoryWEB
- github.com/advisories/GHSA-78vx-ggch-wghmghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2012-3442ghsaADVISORY
- www.mandriva.com/security/advisoriesnvdBroken LinkWEB
- www.ubuntu.com/usn/USN-1560-1nvdBroken LinkWEB
- github.com/django/django/commit/4dea4883e6c50d75f215a6b9bcbd95273f57c72dghsaWEB
- github.com/django/django/commit/e34685034b60be1112160e76091e5aee60149fa1ghsaWEB
- github.com/pypa/advisory-database/tree/main/vulns/django/PYSEC-2012-2.yamlghsaWEB
- www.djangoproject.com/weblog/2012/jul/30/security-releases-issuedghsaWEB
News mentions
0No linked articles in our index yet.