VYPR
Moderate severityNVD Advisory· Published Aug 3, 2018· Updated Aug 5, 2024

CVE-2018-14574

CVE-2018-14574

Description

django.middleware.common.CommonMiddleware in Django 1.11.x before 1.11.15 and 2.0.x before 2.0.8 has an Open Redirect.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Django's `CommonMiddleware` contains an open redirect vulnerability in versions 1.11.x before 1.11.15 and 2.0.x before 2.0.8.

Vulnerability

In Django versions 1.11.x before 1.11.15 and 2.0.x before 2.0.8, the django.middleware.common.CommonMiddleware does not properly sanitize URLs, allowing an open redirect attack. An attacker can craft a URL with leading slashes or scheme-relative URLs that redirect users to an external malicious site [2][3].

Exploitation

An attacker can exploit this vulnerability by creating a crafted URL that, when processed by CommonMiddleware, results in a redirect to an arbitrary external domain. No authentication or special privileges are required; the attacker only needs to lure a user to click on the malicious link [3].

Impact

Successful exploitation allows an attacker to redirect users to an attacker-controlled website, potentially leading to phishing attacks or other social engineering exploits. This is an open redirect vulnerability, which can be used to bypass URL validation and trust mechanisms [3].

Mitigation

The vulnerability is fixed in Django versions 1.11.15 and 2.0.8. Users should upgrade to these versions or later. The fix involves escaping leading slashes to prevent scheme-relative URLs [4]. No workarounds are available for unpatched versions.

AI Insight generated on May 22, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
DjangoPyPI
>= 2.0, < 2.0.82.0.8
DjangoPyPI
>= 1.11, < 1.11.151.11.15

Affected products

350

Patches

5
b83b44f40ca1

[2.0.x] Bumped version for 2.0.8 release.

https://github.com/django/djangoTim GrahamAug 1, 2018via osv
1 file changed · +1 1
  • django/__init__.py+1 1 modified
    @@ -1,6 +1,6 @@
     from django.utils.version import get_version
     
    -VERSION = (2, 0, 8, 'alpha', 0)
    +VERSION = (2, 0, 8, 'final', 0)
     
     __version__ = get_version(VERSION)
     
    
6010da2fbda5

[1.11.x] Bumped version for 1.11.15 release.

https://github.com/django/djangoTim GrahamAug 1, 2018via osv
1 file changed · +1 1
  • django/__init__.py+1 1 modified
    @@ -2,7 +2,7 @@
     
     from django.utils.version import get_version
     
    -VERSION = (1, 11, 15, 'alpha', 0)
    +VERSION = (1, 11, 15, 'final', 0)
     
     __version__ = get_version(VERSION)
     
    
6fffc3c6d420

[2.0.x] Fixed CVE-2018-14574 -- Fixed open redirect possibility in CommonMiddleware.

https://github.com/django/djangoAndreas HugJul 24, 2018via ghsa
8 files changed · +78 8
  • django/middleware/common.py+3 0 modified
    @@ -11,6 +11,7 @@
         cc_delim_re, get_conditional_response, set_response_etag,
     )
     from django.utils.deprecation import MiddlewareMixin, RemovedInDjango21Warning
    +from django.utils.http import escape_leading_slashes
     
     
     class CommonMiddleware(MiddlewareMixin):
    @@ -88,6 +89,8 @@ def get_full_path_with_slash(self, request):
             POST, PUT, or PATCH.
             """
             new_path = request.get_full_path(force_append_slash=True)
    +        # Prevent construction of scheme relative urls.
    +        new_path = escape_leading_slashes(new_path)
             if settings.DEBUG and request.method in ('POST', 'PUT', 'PATCH'):
                 raise RuntimeError(
                     "You called this URL via %(method)s, but the URL doesn't end "
    
  • django/urls/resolvers.py+2 4 modified
    @@ -17,7 +17,7 @@
     from django.core.exceptions import ImproperlyConfigured
     from django.utils.datastructures import MultiValueDict
     from django.utils.functional import cached_property
    -from django.utils.http import RFC3986_SUBDELIMS
    +from django.utils.http import RFC3986_SUBDELIMS, escape_leading_slashes
     from django.utils.regex_helper import normalize
     from django.utils.translation import get_language
     
    @@ -604,9 +604,7 @@ def _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs):
                         # safe characters from `pchar` definition of RFC 3986
                         url = quote(candidate_pat % text_candidate_subs, safe=RFC3986_SUBDELIMS + '/~:@')
                         # Don't allow construction of scheme relative urls.
    -                    if url.startswith('//'):
    -                        url = '/%%2F%s' % url[2:]
    -                    return url
    +                    return escape_leading_slashes(url)
             # lookup_view can be URL name or callable, but callables are not
             # friendly in error messages.
             m = getattr(lookup_view, '__module__', None)
    
  • django/utils/http.py+11 0 modified
    @@ -437,3 +437,14 @@ def limited_parse_qsl(qs, keep_blank_values=False, encoding='utf-8',
                 value = unquote(value, encoding=encoding, errors=errors)
                 r.append((name, value))
         return r
    +
    +
    +def escape_leading_slashes(url):
    +    """
    +    If redirecting to an absolute path (two leading slashes), a slash must be
    +    escaped to prevent browsers from handling the path as schemaless and
    +    redirecting to another host.
    +    """
    +    if url.startswith('//'):
    +        url = '/%2F{}'.format(url[2:])
    +    return url
    
  • docs/releases/1.11.15.txt+13 0 modified
    @@ -5,3 +5,16 @@ Django 1.11.15 release notes
     *August 1, 2018*
     
     Django 1.11.15 fixes a security issue in 1.11.14.
    +
    +CVE-2018-14574: Open redirect possibility in ``CommonMiddleware``
    +=================================================================
    +
    +If the :class:`~django.middleware.common.CommonMiddleware` and the
    +:setting:`APPEND_SLASH` setting are both enabled, and if the project has a
    +URL pattern that accepts any path ending in a slash (many content management
    +systems have such a pattern), then a request to a maliciously crafted URL of
    +that site could lead to a redirect to another site, enabling phishing and other
    +attacks.
    +
    +``CommonMiddleware`` now escapes leading slashes to prevent redirects to other
    +domains.
    
  • docs/releases/2.0.8.txt+13 0 modified
    @@ -6,6 +6,19 @@ Django 2.0.8 release notes
     
     Django 2.0.8 fixes a security issue and several bugs in 2.0.7.
     
    +CVE-2018-14574: Open redirect possibility in ``CommonMiddleware``
    +=================================================================
    +
    +If the :class:`~django.middleware.common.CommonMiddleware` and the
    +:setting:`APPEND_SLASH` setting are both enabled, and if the project has a
    +URL pattern that accepts any path ending in a slash (many content management
    +systems have such a pattern), then a request to a maliciously crafted URL of
    +that site could lead to a redirect to another site, enabling phishing and other
    +attacks.
    +
    +``CommonMiddleware`` now escapes leading slashes to prevent redirects to other
    +domains.
    +
     Bugfixes
     ========
     
    
  • tests/middleware/tests.py+19 0 modified
    @@ -133,6 +133,25 @@ def test_append_slash_quoted(self):
             self.assertEqual(r.status_code, 301)
             self.assertEqual(r.url, '/needsquoting%23/')
     
    +    @override_settings(APPEND_SLASH=True)
    +    def test_append_slash_leading_slashes(self):
    +        """
    +        Paths starting with two slashes are escaped to prevent open redirects.
    +        If there's a URL pattern that allows paths to start with two slashes, a
    +        request with path //evil.com must not redirect to //evil.com/ (appended
    +        slash) which is a schemaless absolute URL. The browser would navigate
    +        to evil.com/.
    +        """
    +        # Use 4 slashes because of RequestFactory behavior.
    +        request = self.rf.get('////evil.com/security')
    +        response = HttpResponseNotFound()
    +        r = CommonMiddleware().process_request(request)
    +        self.assertEqual(r.status_code, 301)
    +        self.assertEqual(r.url, '/%2Fevil.com/security/')
    +        r = CommonMiddleware().process_response(request, response)
    +        self.assertEqual(r.status_code, 301)
    +        self.assertEqual(r.url, '/%2Fevil.com/security/')
    +
         @override_settings(APPEND_SLASH=False, PREPEND_WWW=True)
         def test_prepend_www(self):
             request = self.rf.get('/path/')
    
  • tests/middleware/urls.py+2 0 modified
    @@ -6,4 +6,6 @@
         url(r'^noslash$', views.empty_view),
         url(r'^slash/$', views.empty_view),
         url(r'^needsquoting#/$', views.empty_view),
    +    # Accepts paths with two leading slashes.
    +    url(r'^(.+)/security/$', views.empty_view),
     ]
    
  • tests/utils_tests/test_http.py+15 4 modified
    @@ -6,10 +6,10 @@
     from django.utils.datastructures import MultiValueDict
     from django.utils.deprecation import RemovedInDjango21Warning
     from django.utils.http import (
    -    base36_to_int, cookie_date, http_date, int_to_base36, is_safe_url,
    -    is_same_domain, parse_etags, parse_http_date, quote_etag, urlencode,
    -    urlquote, urlquote_plus, urlsafe_base64_decode, urlsafe_base64_encode,
    -    urlunquote, urlunquote_plus,
    +    base36_to_int, cookie_date, escape_leading_slashes, http_date,
    +    int_to_base36, is_safe_url, is_same_domain, parse_etags, parse_http_date,
    +    quote_etag, urlencode, urlquote, urlquote_plus, urlsafe_base64_decode,
    +    urlsafe_base64_encode, urlunquote, urlunquote_plus,
     )
     
     
    @@ -275,3 +275,14 @@ def test_parsing_rfc850(self):
         def test_parsing_asctime(self):
             parsed = parse_http_date('Sun Nov  6 08:49:37 1994')
             self.assertEqual(datetime.utcfromtimestamp(parsed), datetime(1994, 11, 6, 8, 49, 37))
    +
    +
    +class EscapeLeadingSlashesTests(unittest.TestCase):
    +    def test(self):
    +        tests = (
    +            ('//example.com', '/%2Fexample.com'),
    +            ('//', '/%2F'),
    +        )
    +        for url, expected in tests:
    +            with self.subTest(url=url):
    +                self.assertEqual(escape_leading_slashes(url), expected)
    
d6eaee092709

[1.11.x] Fixed CVE-2018-14574 -- Fixed open redirect possibility in CommonMiddleware.

https://github.com/django/djangoAndreas HugJul 24, 2018via ghsa
7 files changed · +62 4
  • django/middleware/common.py+3 0 modified
    @@ -11,6 +11,7 @@
     )
     from django.utils.deprecation import MiddlewareMixin, RemovedInDjango21Warning
     from django.utils.encoding import force_text
    +from django.utils.http import escape_leading_slashes
     from django.utils.six.moves.urllib.parse import urlparse
     
     
    @@ -90,6 +91,8 @@ def get_full_path_with_slash(self, request):
             POST, PUT, or PATCH.
             """
             new_path = request.get_full_path(force_append_slash=True)
    +        # Prevent construction of scheme relative urls.
    +        new_path = escape_leading_slashes(new_path)
             if settings.DEBUG and request.method in ('POST', 'PUT', 'PATCH'):
                 raise RuntimeError(
                     "You called this URL via %(method)s, but the URL doesn't end "
    
  • django/urls/resolvers.py+4 4 modified
    @@ -20,7 +20,9 @@
     from django.utils.datastructures import MultiValueDict
     from django.utils.encoding import force_str, force_text
     from django.utils.functional import cached_property
    -from django.utils.http import RFC3986_SUBDELIMS, urlquote
    +from django.utils.http import (
    +    RFC3986_SUBDELIMS, escape_leading_slashes, urlquote,
    +)
     from django.utils.regex_helper import normalize
     from django.utils.translation import get_language
     
    @@ -465,9 +467,7 @@ def _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs):
                         # safe characters from `pchar` definition of RFC 3986
                         url = urlquote(candidate_pat % candidate_subs, safe=RFC3986_SUBDELIMS + str('/~:@'))
                         # Don't allow construction of scheme relative urls.
    -                    if url.startswith('//'):
    -                        url = '/%%2F%s' % url[2:]
    -                    return url
    +                    return escape_leading_slashes(url)
             # lookup_view can be URL name or callable, but callables are not
             # friendly in error messages.
             m = getattr(lookup_view, '__module__', None)
    
  • django/utils/http.py+11 0 modified
    @@ -466,3 +466,14 @@ def limited_parse_qsl(qs, keep_blank_values=False, encoding='utf-8',
                     value = unquote(nv[1].replace(b'+', b' '))
                 r.append((name, value))
         return r
    +
    +
    +def escape_leading_slashes(url):
    +    """
    +    If redirecting to an absolute path (two leading slashes), a slash must be
    +    escaped to prevent browsers from handling the path as schemaless and
    +    redirecting to another host.
    +    """
    +    if url.startswith('//'):
    +        url = '/%2F{}'.format(url[2:])
    +    return url
    
  • docs/releases/1.11.15.txt+13 0 modified
    @@ -5,3 +5,16 @@ Django 1.11.15 release notes
     *August 1, 2018*
     
     Django 1.11.15 fixes a security issue in 1.11.14.
    +
    +CVE-2018-14574: Open redirect possibility in ``CommonMiddleware``
    +=================================================================
    +
    +If the :class:`~django.middleware.common.CommonMiddleware` and the
    +:setting:`APPEND_SLASH` setting are both enabled, and if the project has a
    +URL pattern that accepts any path ending in a slash (many content management
    +systems have such a pattern), then a request to a maliciously crafted URL of
    +that site could lead to a redirect to another site, enabling phishing and other
    +attacks.
    +
    +``CommonMiddleware`` now escapes leading slashes to prevent redirects to other
    +domains.
    
  • tests/middleware/tests.py+19 0 modified
    @@ -137,6 +137,25 @@ def test_append_slash_quoted(self):
             self.assertEqual(r.status_code, 301)
             self.assertEqual(r.url, '/needsquoting%23/')
     
    +    @override_settings(APPEND_SLASH=True)
    +    def test_append_slash_leading_slashes(self):
    +        """
    +        Paths starting with two slashes are escaped to prevent open redirects.
    +        If there's a URL pattern that allows paths to start with two slashes, a
    +        request with path //evil.com must not redirect to //evil.com/ (appended
    +        slash) which is a schemaless absolute URL. The browser would navigate
    +        to evil.com/.
    +        """
    +        # Use 4 slashes because of RequestFactory behavior.
    +        request = self.rf.get('////evil.com/security')
    +        response = HttpResponseNotFound()
    +        r = CommonMiddleware().process_request(request)
    +        self.assertEqual(r.status_code, 301)
    +        self.assertEqual(r.url, '/%2Fevil.com/security/')
    +        r = CommonMiddleware().process_response(request, response)
    +        self.assertEqual(r.status_code, 301)
    +        self.assertEqual(r.url, '/%2Fevil.com/security/')
    +
         @override_settings(APPEND_SLASH=False, PREPEND_WWW=True)
         def test_prepend_www(self):
             request = self.rf.get('/path/')
    
  • tests/middleware/urls.py+2 0 modified
    @@ -6,4 +6,6 @@
         url(r'^noslash$', views.empty_view),
         url(r'^slash/$', views.empty_view),
         url(r'^needsquoting#/$', views.empty_view),
    +    # Accepts paths with two leading slashes.
    +    url(r'^(.+)/security/$', views.empty_view),
     ]
    
  • tests/utils_tests/test_http.py+10 0 modified
    @@ -248,3 +248,13 @@ def test_parsing_rfc850(self):
         def test_parsing_asctime(self):
             parsed = http.parse_http_date('Sun Nov  6 08:49:37 1994')
             self.assertEqual(datetime.utcfromtimestamp(parsed), datetime(1994, 11, 6, 8, 49, 37))
    +
    +
    +class EscapeLeadingSlashesTests(unittest.TestCase):
    +    def test(self):
    +        tests = (
    +            ('//example.com', '/%2Fexample.com'),
    +            ('//', '/%2F'),
    +        )
    +        for url, expected in tests:
    +            self.assertEqual(http.escape_leading_slashes(url), expected)
    
c4e5ff7fdb5f

[2.1.x] Fixed CVE-2018-14574 -- Fixed open redirect possibility in CommonMiddleware.

https://github.com/django/djangoAndreas HugJul 24, 2018via ghsa
8 files changed · +78 8
  • django/middleware/common.py+3 0 modified
    @@ -7,6 +7,7 @@
     from django.http import HttpResponsePermanentRedirect
     from django.urls import is_valid_path
     from django.utils.deprecation import MiddlewareMixin
    +from django.utils.http import escape_leading_slashes
     
     
     class CommonMiddleware(MiddlewareMixin):
    @@ -79,6 +80,8 @@ def get_full_path_with_slash(self, request):
             POST, PUT, or PATCH.
             """
             new_path = request.get_full_path(force_append_slash=True)
    +        # Prevent construction of scheme relative urls.
    +        new_path = escape_leading_slashes(new_path)
             if settings.DEBUG and request.method in ('POST', 'PUT', 'PATCH'):
                 raise RuntimeError(
                     "You called this URL via %(method)s, but the URL doesn't end "
    
  • django/urls/resolvers.py+2 4 modified
    @@ -17,7 +17,7 @@
     from django.core.exceptions import ImproperlyConfigured
     from django.utils.datastructures import MultiValueDict
     from django.utils.functional import cached_property
    -from django.utils.http import RFC3986_SUBDELIMS
    +from django.utils.http import RFC3986_SUBDELIMS, escape_leading_slashes
     from django.utils.regex_helper import normalize
     from django.utils.translation import get_language
     
    @@ -592,9 +592,7 @@ def _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs):
                         # safe characters from `pchar` definition of RFC 3986
                         url = quote(candidate_pat % text_candidate_subs, safe=RFC3986_SUBDELIMS + '/~:@')
                         # Don't allow construction of scheme relative urls.
    -                    if url.startswith('//'):
    -                        url = '/%%2F%s' % url[2:]
    -                    return url
    +                    return escape_leading_slashes(url)
             # lookup_view can be URL name or callable, but callables are not
             # friendly in error messages.
             m = getattr(lookup_view, '__module__', None)
    
  • django/utils/http.py+11 0 modified
    @@ -433,3 +433,14 @@ def limited_parse_qsl(qs, keep_blank_values=False, encoding='utf-8',
                 value = unquote(value, encoding=encoding, errors=errors)
                 r.append((name, value))
         return r
    +
    +
    +def escape_leading_slashes(url):
    +    """
    +    If redirecting to an absolute path (two leading slashes), a slash must be
    +    escaped to prevent browsers from handling the path as schemaless and
    +    redirecting to another host.
    +    """
    +    if url.startswith('//'):
    +        url = '/%2F{}'.format(url[2:])
    +    return url
    
  • docs/releases/1.11.15.txt+13 0 modified
    @@ -5,3 +5,16 @@ Django 1.11.15 release notes
     *August 1, 2018*
     
     Django 1.11.15 fixes a security issue in 1.11.14.
    +
    +CVE-2018-14574: Open redirect possibility in ``CommonMiddleware``
    +=================================================================
    +
    +If the :class:`~django.middleware.common.CommonMiddleware` and the
    +:setting:`APPEND_SLASH` setting are both enabled, and if the project has a
    +URL pattern that accepts any path ending in a slash (many content management
    +systems have such a pattern), then a request to a maliciously crafted URL of
    +that site could lead to a redirect to another site, enabling phishing and other
    +attacks.
    +
    +``CommonMiddleware`` now escapes leading slashes to prevent redirects to other
    +domains.
    
  • docs/releases/2.0.8.txt+13 0 modified
    @@ -6,6 +6,19 @@ Django 2.0.8 release notes
     
     Django 2.0.8 fixes a security issue and several bugs in 2.0.7.
     
    +CVE-2018-14574: Open redirect possibility in ``CommonMiddleware``
    +=================================================================
    +
    +If the :class:`~django.middleware.common.CommonMiddleware` and the
    +:setting:`APPEND_SLASH` setting are both enabled, and if the project has a
    +URL pattern that accepts any path ending in a slash (many content management
    +systems have such a pattern), then a request to a maliciously crafted URL of
    +that site could lead to a redirect to another site, enabling phishing and other
    +attacks.
    +
    +``CommonMiddleware`` now escapes leading slashes to prevent redirects to other
    +domains.
    +
     Bugfixes
     ========
     
    
  • tests/middleware/tests.py+19 0 modified
    @@ -130,6 +130,25 @@ def test_append_slash_quoted(self):
             self.assertEqual(r.status_code, 301)
             self.assertEqual(r.url, '/needsquoting%23/')
     
    +    @override_settings(APPEND_SLASH=True)
    +    def test_append_slash_leading_slashes(self):
    +        """
    +        Paths starting with two slashes are escaped to prevent open redirects.
    +        If there's a URL pattern that allows paths to start with two slashes, a
    +        request with path //evil.com must not redirect to //evil.com/ (appended
    +        slash) which is a schemaless absolute URL. The browser would navigate
    +        to evil.com/.
    +        """
    +        # Use 4 slashes because of RequestFactory behavior.
    +        request = self.rf.get('////evil.com/security')
    +        response = HttpResponseNotFound()
    +        r = CommonMiddleware().process_request(request)
    +        self.assertEqual(r.status_code, 301)
    +        self.assertEqual(r.url, '/%2Fevil.com/security/')
    +        r = CommonMiddleware().process_response(request, response)
    +        self.assertEqual(r.status_code, 301)
    +        self.assertEqual(r.url, '/%2Fevil.com/security/')
    +
         @override_settings(APPEND_SLASH=False, PREPEND_WWW=True)
         def test_prepend_www(self):
             request = self.rf.get('/path/')
    
  • tests/middleware/urls.py+2 0 modified
    @@ -6,4 +6,6 @@
         url(r'^noslash$', views.empty_view),
         url(r'^slash/$', views.empty_view),
         url(r'^needsquoting#/$', views.empty_view),
    +    # Accepts paths with two leading slashes.
    +    url(r'^(.+)/security/$', views.empty_view),
     ]
    
  • tests/utils_tests/test_http.py+15 4 modified
    @@ -5,10 +5,10 @@
     from django.utils.datastructures import MultiValueDict
     from django.utils.deprecation import RemovedInDjango30Warning
     from django.utils.http import (
    -    base36_to_int, cookie_date, http_date, int_to_base36, is_safe_url,
    -    is_same_domain, parse_etags, parse_http_date, quote_etag, urlencode,
    -    urlquote, urlquote_plus, urlsafe_base64_decode, urlsafe_base64_encode,
    -    urlunquote, urlunquote_plus,
    +    base36_to_int, cookie_date, escape_leading_slashes, http_date,
    +    int_to_base36, is_safe_url, is_same_domain, parse_etags, parse_http_date,
    +    quote_etag, urlencode, urlquote, urlquote_plus, urlsafe_base64_decode,
    +    urlsafe_base64_encode, urlunquote, urlunquote_plus,
     )
     
     
    @@ -271,3 +271,14 @@ def test_parsing_rfc850(self):
         def test_parsing_asctime(self):
             parsed = parse_http_date('Sun Nov  6 08:49:37 1994')
             self.assertEqual(datetime.utcfromtimestamp(parsed), datetime(1994, 11, 6, 8, 49, 37))
    +
    +
    +class EscapeLeadingSlashesTests(unittest.TestCase):
    +    def test(self):
    +        tests = (
    +            ('//example.com', '/%2Fexample.com'),
    +            ('//', '/%2F'),
    +        )
    +        for url, expected in tests:
    +            with self.subTest(url=url):
    +                self.assertEqual(escape_leading_slashes(url), expected)
    

Vulnerability mechanics

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

References

16

News mentions

0

No linked articles in our index yet.