CVE-2015-2317
Description
The utils.http.is_safe_url function in Django before 1.4.20, 1.5.x, 1.6.x before 1.6.11, 1.7.x before 1.7.7, and 1.8.x before 1.8c1 does not properly validate URLs, which allows remote attackers to conduct cross-site scripting (XSS) attacks via a control character in a URL, as demonstrated by a \x08javascript: URL.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
DjangoPyPI | < 1.4.20 | 1.4.20 |
DjangoPyPI | >= 1.5, < 1.6.11 | 1.6.11 |
DjangoPyPI | >= 1.7, < 1.7.7 | 1.7.7 |
DjangoPyPI | >= 1.8a1, < 1.8c1 | 1.8c1 |
Patches
42342693b31f7[1.4.x] Made is_safe_url() reject URLs that start with control characters.
3 files changed · +30 −2
django/utils/http.py+8 −1 modified@@ -4,6 +4,7 @@ import sys import urllib import urlparse +import unicodedata from email.utils import formatdate from django.utils.datastructures import MultiValueDict @@ -232,9 +233,10 @@ def is_safe_url(url, host=None): Always returns ``False`` on an empty url. """ + if url is not None: + url = url.strip() if not url: return False - url = url.strip() # Chrome treats \ completely as / url = url.replace('\\', '/') # Chrome considers any URL with more than two slashes to be absolute, but @@ -248,5 +250,10 @@ def is_safe_url(url, host=None): # allow this syntax. if not url_info[1] and url_info[0]: return False + # Forbid URLs that start with control characters. Some browsers (like + # Chrome) ignore quite a few control characters at the start of a + # URL and might consider the URL as scheme relative. + if unicodedata.category(unicode(url[0]))[0] == 'C': + return False return (not url_info[1] or url_info[1] == host) and \ (not url_info[0] or url_info[0] in ['http', 'https'])
docs/releases/1.4.20.txt+19 −0 modified@@ -5,3 +5,22 @@ Django 1.4.20 release notes *March 18, 2015* Django 1.4.20 fixes one security issue in 1.4.19. + +Mitigated possible XSS attack via user-supplied redirect URLs +============================================================= + +Django relies on user input in some cases (e.g. +:func:`django.contrib.auth.views.login` and :doc:`i18n </topics/i18n/index>`) +to redirect the user to an "on success" URL. The security checks for these +redirects (namely ``django.utils.http.is_safe_url()``) accepted URLs with +leading control characters and so considered URLs like ``\x08javascript:...`` +safe. This issue doesn't affect Django currently, since we only put this URL +into the ``Location`` response header and browsers seem to ignore JavaScript +there. Browsers we tested also treat URLs prefixed with control characters such +as ``%08//example.com`` as relative paths so redirection to an unsafe target +isn't a problem either. + +However, if a developer relies on ``is_safe_url()`` to +provide safe redirect targets and puts such a URL into a link, they could +suffer from an XSS attack as some browsers such as Google Chrome ignore control +characters at the start of a URL in an anchor ``href``.
tests/regressiontests/utils/http.py+3 −1 modified@@ -98,7 +98,9 @@ def test_is_safe_url(self): 'http:\/example.com', 'http:/\example.com', 'javascript:alert("XSS")' - '\njavascript:alert(x)'): + '\njavascript:alert(x)', + '\x08//example.com', + '\n'): self.assertFalse(http.is_safe_url(bad_url, host='testserver'), "%s should be blocked" % bad_url) for good_url in ('/view/?param=http://example.com', '/view/?param=https://example.com',
2a4113dbd532[1.7.x] Made is_safe_url() reject URLs that start with control characters.
5 files changed · +68 −3
django/utils/http.py+8 −2 modified@@ -5,7 +5,7 @@ import datetime import re import sys - +import unicodedata from binascii import Error as BinasciiError from email.utils import formatdate @@ -270,9 +270,10 @@ def is_safe_url(url, host=None): Always returns ``False`` on an empty url. """ + if url is not None: + url = url.strip() if not url: return False - url = url.strip() # Chrome treats \ completely as / url = url.replace('\\', '/') # Chrome considers any URL with more than two slashes to be absolute, but @@ -286,5 +287,10 @@ def is_safe_url(url, host=None): # allow this syntax. if not url_info.netloc and url_info.scheme: return False + # Forbid URLs that start with control characters. Some browsers (like + # Chrome) ignore quite a few control characters at the start of a + # URL and might consider the URL as scheme relative. + if unicodedata.category(url[0])[0] == 'C': + return False return ((not url_info.netloc or url_info.netloc == host) and (not url_info.scheme or url_info.scheme in ['http', 'https']))
docs/releases/1.4.20.txt+19 −0 modified@@ -5,3 +5,22 @@ Django 1.4.20 release notes *March 18, 2015* Django 1.4.20 fixes one security issue in 1.4.19. + +Mitigated possible XSS attack via user-supplied redirect URLs +============================================================= + +Django relies on user input in some cases (e.g. +:func:`django.contrib.auth.views.login` and :doc:`i18n </topics/i18n/index>`) +to redirect the user to an "on success" URL. The security checks for these +redirects (namely ``django.utils.http.is_safe_url()``) accepted URLs with +leading control characters and so considered URLs like ``\x08javascript:...`` +safe. This issue doesn't affect Django currently, since we only put this URL +into the ``Location`` response header and browsers seem to ignore JavaScript +there. Browsers we tested also treat URLs prefixed with control characters such +as ``%08//example.com`` as relative paths so redirection to an unsafe target +isn't a problem either. + +However, if a developer relies on ``is_safe_url()`` to +provide safe redirect targets and puts such a URL into a link, they could +suffer from an XSS attack as some browsers such as Google Chrome ignore control +characters at the start of a URL in an anchor ``href``.
docs/releases/1.6.11.txt+19 −0 modified@@ -22,3 +22,22 @@ it detects the length of the string it's processing increases. Remember that absolutely NO guarantee is provided about the results of ``strip_tags()`` being HTML safe. So NEVER mark safe the result of a ``strip_tags()`` call without escaping it first, for example with :func:`~django.utils.html.escape`. + +Mitigated possible XSS attack via user-supplied redirect URLs +============================================================= + +Django relies on user input in some cases (e.g. +:func:`django.contrib.auth.views.login` and :doc:`i18n </topics/i18n/index>`) +to redirect the user to an "on success" URL. The security checks for these +redirects (namely ``django.utils.http.is_safe_url()``) accepted URLs with +leading control characters and so considered URLs like ``\x08javascript:...`` +safe. This issue doesn't affect Django currently, since we only put this URL +into the ``Location`` response header and browsers seem to ignore JavaScript +there. Browsers we tested also treat URLs prefixed with control characters such +as ``%08//example.com`` as relative paths so redirection to an unsafe target +isn't a problem either. + +However, if a developer relies on ``is_safe_url()`` to +provide safe redirect targets and puts such a URL into a link, they could +suffer from an XSS attack as some browsers such as Google Chrome ignore control +characters at the start of a URL in an anchor ``href``.
docs/releases/1.7.7.txt+19 −0 modified@@ -23,6 +23,25 @@ absolutely NO guarantee is provided about the results of ``strip_tags()`` being HTML safe. So NEVER mark safe the result of a ``strip_tags()`` call without escaping it first, for example with :func:`~django.utils.html.escape`. +Mitigated possible XSS attack via user-supplied redirect URLs +============================================================= + +Django relies on user input in some cases (e.g. +:func:`django.contrib.auth.views.login` and :doc:`i18n </topics/i18n/index>`) +to redirect the user to an "on success" URL. The security checks for these +redirects (namely ``django.utils.http.is_safe_url()``) accepted URLs with +leading control characters and so considered URLs like ``\x08javascript:...`` +safe. This issue doesn't affect Django currently, since we only put this URL +into the ``Location`` response header and browsers seem to ignore JavaScript +there. Browsers we tested also treat URLs prefixed with control characters such +as ``%08//example.com`` as relative paths so redirection to an unsafe target +isn't a problem either. + +However, if a developer relies on ``is_safe_url()`` to +provide safe redirect targets and puts such a URL into a link, they could +suffer from an XSS attack as some browsers such as Google Chrome ignore control +characters at the start of a URL in an anchor ``href``. + Bugfixes ========
tests/utils_tests/test_http.py+3 −1 modified@@ -108,7 +108,9 @@ def test_is_safe_url(self): 'http:\/example.com', 'http:/\example.com', 'javascript:alert("XSS")', - '\njavascript:alert(x)'): + '\njavascript:alert(x)', + '\x08//example.com', + '\n'): self.assertFalse(http.is_safe_url(bad_url, host='testserver'), "%s should be blocked" % bad_url) for good_url in ('/view/?param=http://example.com', '/view/?param=https://example.com',
770427c2896a[1.8.x] Made is_safe_url() reject URLs that start with control characters.
5 files changed · +68 −2
django/utils/http.py+8 −1 modified@@ -5,6 +5,7 @@ import datetime import re import sys +import unicodedata from binascii import Error as BinasciiError from email.utils import formatdate @@ -272,9 +273,10 @@ def is_safe_url(url, host=None): Always returns ``False`` on an empty url. """ + if url is not None: + url = url.strip() if not url: return False - url = url.strip() # Chrome treats \ completely as / url = url.replace('\\', '/') # Chrome considers any URL with more than two slashes to be absolute, but @@ -288,5 +290,10 @@ def is_safe_url(url, host=None): # allow this syntax. if not url_info.netloc and url_info.scheme: return False + # Forbid URLs that start with control characters. Some browsers (like + # Chrome) ignore quite a few control characters at the start of a + # URL and might consider the URL as scheme relative. + if unicodedata.category(url[0])[0] == 'C': + return False return ((not url_info.netloc or url_info.netloc == host) and (not url_info.scheme or url_info.scheme in ['http', 'https']))
docs/releases/1.4.20.txt+19 −0 modified@@ -5,3 +5,22 @@ Django 1.4.20 release notes *March 18, 2015* Django 1.4.20 fixes one security issue in 1.4.19. + +Mitigated possible XSS attack via user-supplied redirect URLs +============================================================= + +Django relies on user input in some cases (e.g. +:func:`django.contrib.auth.views.login` and :doc:`i18n </topics/i18n/index>`) +to redirect the user to an "on success" URL. The security checks for these +redirects (namely ``django.utils.http.is_safe_url()``) accepted URLs with +leading control characters and so considered URLs like ``\x08javascript:...`` +safe. This issue doesn't affect Django currently, since we only put this URL +into the ``Location`` response header and browsers seem to ignore JavaScript +there. Browsers we tested also treat URLs prefixed with control characters such +as ``%08//example.com`` as relative paths so redirection to an unsafe target +isn't a problem either. + +However, if a developer relies on ``is_safe_url()`` to +provide safe redirect targets and puts such a URL into a link, they could +suffer from an XSS attack as some browsers such as Google Chrome ignore control +characters at the start of a URL in an anchor ``href``.
docs/releases/1.6.11.txt+19 −0 modified@@ -22,3 +22,22 @@ it detects the length of the string it's processing increases. Remember that absolutely NO guarantee is provided about the results of ``strip_tags()`` being HTML safe. So NEVER mark safe the result of a ``strip_tags()`` call without escaping it first, for example with :func:`~django.utils.html.escape`. + +Mitigated possible XSS attack via user-supplied redirect URLs +============================================================= + +Django relies on user input in some cases (e.g. +:func:`django.contrib.auth.views.login` and :doc:`i18n </topics/i18n/index>`) +to redirect the user to an "on success" URL. The security checks for these +redirects (namely ``django.utils.http.is_safe_url()``) accepted URLs with +leading control characters and so considered URLs like ``\x08javascript:...`` +safe. This issue doesn't affect Django currently, since we only put this URL +into the ``Location`` response header and browsers seem to ignore JavaScript +there. Browsers we tested also treat URLs prefixed with control characters such +as ``%08//example.com`` as relative paths so redirection to an unsafe target +isn't a problem either. + +However, if a developer relies on ``is_safe_url()`` to +provide safe redirect targets and puts such a URL into a link, they could +suffer from an XSS attack as some browsers such as Google Chrome ignore control +characters at the start of a URL in an anchor ``href``.
docs/releases/1.7.7.txt+19 −0 modified@@ -23,6 +23,25 @@ absolutely NO guarantee is provided about the results of ``strip_tags()`` being HTML safe. So NEVER mark safe the result of a ``strip_tags()`` call without escaping it first, for example with :func:`~django.utils.html.escape`. +Mitigated possible XSS attack via user-supplied redirect URLs +============================================================= + +Django relies on user input in some cases (e.g. +:func:`django.contrib.auth.views.login` and :doc:`i18n </topics/i18n/index>`) +to redirect the user to an "on success" URL. The security checks for these +redirects (namely ``django.utils.http.is_safe_url()``) accepted URLs with +leading control characters and so considered URLs like ``\x08javascript:...`` +safe. This issue doesn't affect Django currently, since we only put this URL +into the ``Location`` response header and browsers seem to ignore JavaScript +there. Browsers we tested also treat URLs prefixed with control characters such +as ``%08//example.com`` as relative paths so redirection to an unsafe target +isn't a problem either. + +However, if a developer relies on ``is_safe_url()`` to +provide safe redirect targets and puts such a URL into a link, they could +suffer from an XSS attack as some browsers such as Google Chrome ignore control +characters at the start of a URL in an anchor ``href``. + Bugfixes ========
tests/utils_tests/test_http.py+3 −1 modified@@ -115,7 +115,9 @@ def test_is_safe_url(self): 'http:\/example.com', 'http:/\example.com', 'javascript:alert("XSS")', - '\njavascript:alert(x)'): + '\njavascript:alert(x)', + '\x08//example.com', + '\n'): self.assertFalse(http.is_safe_url(bad_url, host='testserver'), "%s should be blocked" % bad_url) for good_url in ('/view/?param=http://example.com', '/view/?param=https://example.com',
5510f0707115[1.6.x] Made is_safe_url() reject URLs that start with control characters.
4 files changed · +51 −5
django/utils/http.py+10 −4 modified@@ -5,7 +5,7 @@ import datetime import re import sys - +import unicodedata from binascii import Error as BinasciiError from email.utils import formatdate @@ -254,9 +254,10 @@ def is_safe_url(url, host=None): Always returns ``False`` on an empty url. """ + if url is not None: + url = url.strip() if not url: return False - url = url.strip() # Chrome treats \ completely as / url = url.replace('\\', '/') # Chrome considers any URL with more than two slashes to be absolute, but @@ -270,5 +271,10 @@ def is_safe_url(url, host=None): # allow this syntax. if not url_info.netloc and url_info.scheme: return False - return (not url_info.netloc or url_info.netloc == host) and \ - (not url_info.scheme or url_info.scheme in ['http', 'https']) + # Forbid URLs that start with control characters. Some browsers (like + # Chrome) ignore quite a few control characters at the start of a + # URL and might consider the URL as scheme relative. + if unicodedata.category(url[0])[0] == 'C': + return False + return ((not url_info.netloc or url_info.netloc == host) and + (not url_info.scheme or url_info.scheme in ['http', 'https']))
docs/releases/1.4.20.txt+19 −0 modified@@ -5,3 +5,22 @@ Django 1.4.20 release notes *March 18, 2015* Django 1.4.20 fixes one security issue in 1.4.19. + +Mitigated possible XSS attack via user-supplied redirect URLs +============================================================= + +Django relies on user input in some cases (e.g. +:func:`django.contrib.auth.views.login` and :doc:`i18n </topics/i18n/index>`) +to redirect the user to an "on success" URL. The security checks for these +redirects (namely ``django.utils.http.is_safe_url()``) accepted URLs with +leading control characters and so considered URLs like ``\x08javascript:...`` +safe. This issue doesn't affect Django currently, since we only put this URL +into the ``Location`` response header and browsers seem to ignore JavaScript +there. Browsers we tested also treat URLs prefixed with control characters such +as ``%08//example.com`` as relative paths so redirection to an unsafe target +isn't a problem either. + +However, if a developer relies on ``is_safe_url()`` to +provide safe redirect targets and puts such a URL into a link, they could +suffer from an XSS attack as some browsers such as Google Chrome ignore control +characters at the start of a URL in an anchor ``href``.
docs/releases/1.6.11.txt+19 −0 modified@@ -22,3 +22,22 @@ it detects the length of the string it's processing increases. Remember that absolutely NO guarantee is provided about the results of ``strip_tags()`` being HTML safe. So NEVER mark safe the result of a ``strip_tags()`` call without escaping it first, for example with :func:`~django.utils.html.escape`. + +Mitigated possible XSS attack via user-supplied redirect URLs +============================================================= + +Django relies on user input in some cases (e.g. +:func:`django.contrib.auth.views.login` and :doc:`i18n </topics/i18n/index>`) +to redirect the user to an "on success" URL. The security checks for these +redirects (namely ``django.utils.http.is_safe_url()``) accepted URLs with +leading control characters and so considered URLs like ``\x08javascript:...`` +safe. This issue doesn't affect Django currently, since we only put this URL +into the ``Location`` response header and browsers seem to ignore JavaScript +there. Browsers we tested also treat URLs prefixed with control characters such +as ``%08//example.com`` as relative paths so redirection to an unsafe target +isn't a problem either. + +However, if a developer relies on ``is_safe_url()`` to +provide safe redirect targets and puts such a URL into a link, they could +suffer from an XSS attack as some browsers such as Google Chrome ignore control +characters at the start of a URL in an anchor ``href``.
tests/utils_tests/test_http.py+3 −1 modified@@ -110,7 +110,9 @@ def test_is_safe_url(self): 'http:\/example.com', 'http:/\example.com', 'javascript:alert("XSS")', - '\njavascript:alert(x)'): + '\njavascript:alert(x)', + '\x08//example.com', + '\n'): self.assertFalse(http.is_safe_url(bad_url, host='testserver'), "%s should be blocked" % bad_url) for good_url in ('/view/?param=http://example.com', '/view/?param=https://example.com',
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
19- lists.fedoraproject.org/pipermail/package-announce/2015-April/155421.htmlnvdThird Party AdvisoryWEB
- lists.opensuse.org/opensuse-updates/2015-04/msg00001.htmlnvdThird Party AdvisoryWEB
- ubuntu.com/usn/usn-2539-1nvdThird Party AdvisoryWEB
- www.debian.org/security/2015/dsa-3204nvdThird Party AdvisoryWEB
- www.oracle.com/technetwork/topics/security/bulletinapr2015-2511959.htmlnvdThird Party AdvisoryWEB
- github.com/advisories/GHSA-7fq8-4pv5-5w5cghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2015-2317ghsaADVISORY
- www.djangoproject.com/weblog/2015/mar/18/security-releases/nvdVendor Advisory
- lists.fedoraproject.org/pipermail/package-announce/2015-June/160263.htmlnvdWEB
- lists.opensuse.org/opensuse-updates/2015-09/msg00035.htmlnvdWEB
- www.mandriva.com/security/advisoriesnvdBroken LinkWEB
- github.com/django/django/commit/2342693b31f740a422abf7267c53b4e7bc487c1bghsaWEB
- github.com/django/django/commit/2a4113dbd532ce952308992633d802dc169a75f1ghsaWEB
- github.com/django/django/commit/5510f070711540aaa8d3707776cd77494e688ef9ghsaWEB
- github.com/django/django/commit/770427c2896a078925abfca2317486b284d22f04ghsaWEB
- github.com/pypa/advisory-database/tree/main/vulns/django/PYSEC-2015-9.yamlghsaWEB
- web.archive.org/web/20200228131706/http://www.securityfocus.com/bid/73319ghsaWEB
- www.djangoproject.com/weblog/2015/mar/18/security-releasesghsaWEB
- www.securityfocus.com/bid/73319nvd
News mentions
0No linked articles in our index yet.