VYPR
Moderate severityNVD Advisory· Published Mar 25, 2015· Updated May 6, 2026

CVE-2015-2317

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.

PackageAffected versionsPatched versions
DjangoPyPI
< 1.4.201.4.20
DjangoPyPI
>= 1.5, < 1.6.111.6.11
DjangoPyPI
>= 1.7, < 1.7.71.7.7
DjangoPyPI
>= 1.8a1, < 1.8c11.8c1

Patches

4
2342693b31f7

[1.4.x] Made is_safe_url() reject URLs that start with control characters.

https://github.com/django/djangoTim GrahamMar 10, 2015via ghsa
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.

https://github.com/django/djangoTim GrahamMar 10, 2015via ghsa
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.

https://github.com/django/djangoTim GrahamMar 10, 2015via ghsa
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.

https://github.com/django/djangoTim GrahamMar 10, 2015via ghsa
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

News mentions

0

No linked articles in our index yet.