VYPR
High severity7.5NVD Advisory· Published Oct 3, 2016· Updated May 6, 2026

CVE-2016-7401

CVE-2016-7401

Description

The cookie parsing code in Django before 1.8.15 and 1.9.x before 1.9.10, when used on a site with Google Analytics, allows remote attackers to bypass an intended CSRF protection mechanism by setting arbitrary cookies.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
DjangoPyPI
< 1.8.151.8.15
DjangoPyPI
>= 1.9, < 1.9.101.9.10

Patches

3
6fe846a8f08d

Added CVE-2016-7401 to the security release archive.

https://github.com/django/djangoTim GrahamSep 26, 2016via ghsa
1 file changed · +12 0
  • docs/releases/security.txt+12 0 modified
    @@ -769,3 +769,15 @@ Versions affected
     
     * Django 1.9 `(patch) <https://github.com/django/django/commit/d03bf6fe4e9bf5b07de62c1a271c4b41a7d3d158>`__
     * Django 1.8 `(patch) <https://github.com/django/django/commit/f68e5a99164867ab0e071a936470958ed867479d>`__
    +
    +September 26, 2016 - :cve:`2016-7401`
    +-------------------------------------
    +
    +CSRF protection bypass on a site with Google Analytics. `Full description
    +<https://www.djangoproject.com/weblog/2016/sep/26/security-releases/>`__
    +
    +Versions affected
    +~~~~~~~~~~~~~~~~~
    +
    +* Django 1.9 `(patch) <https://github.com/django/django/commit/d1bc980db1c0fffd6d60677e62f70beadb9fe64a>`__
    +* Django 1.8 `(patch) <https://github.com/django/django/commit/6118ab7d0676f0d622278e5be215f14fb5410b6a>`__
    
6118ab7d0676

[1.8.x] Fixed CVE-2016-7401 -- Fixed CSRF protection bypass on a site with Google Analytics.

https://github.com/django/djangoCollin AndersonMar 12, 2016via ghsa
5 files changed · +87 18
  • django/http/cookie.py+16 13 modified
    @@ -89,18 +89,21 @@ def _BaseCookie__set(self, key, real_value, coded_value):
     
     
     def parse_cookie(cookie):
    -    if cookie == '':
    -        return {}
    -    if not isinstance(cookie, http_cookies.BaseCookie):
    -        try:
    -            c = SimpleCookie()
    -            c.load(cookie)
    -        except http_cookies.CookieError:
    -            # Invalid cookie
    -            return {}
    -    else:
    -        c = cookie
    +    """
    +    Return a dictionary parsed from a `Cookie:` header string.
    +    """
         cookiedict = {}
    -    for key in c.keys():
    -        cookiedict[key] = c.get(key).value
    +    if six.PY2:
    +        cookie = force_str(cookie)
    +    for chunk in cookie.split(str(';')):
    +        if str('=') in chunk:
    +            key, val = chunk.split(str('='), 1)
    +        else:
    +            # Assume an empty name per
    +            # https://bugzilla.mozilla.org/show_bug.cgi?id=169091
    +            key, val = str(''), chunk
    +        key, val = key.strip(), val.strip()
    +        if key or val:
    +            # unquote using Python's algorithm.
    +            cookiedict[key] = http_cookies._unquote(val)
         return cookiedict
    
  • docs/releases/1.8.15.txt+18 0 added
    @@ -0,0 +1,18 @@
    +===========================
    +Django 1.8.15 release notes
    +===========================
    +
    +*September 26, 2016*
    +
    +Django 1.8.15 fixes a security issue in 1.8.14.
    +
    +CSRF protection bypass on a site with Google Analytics
    +======================================================
    +
    +An interaction between Google Analytics and Django's cookie parsing could allow
    +an attacker to set arbitrary cookies leading to a bypass of CSRF protection.
    +
    +The parser for ``request.COOKIES`` is simplified to better match the behavior
    +of browsers and to mitigate this attack. ``request.COOKIES`` may now contain
    +cookies that are invalid according to :rfc:`6265` but are possible to set via
    +``document.cookie``.
    
  • docs/releases/index.txt+1 0 modified
    @@ -25,6 +25,7 @@ versions of the documentation contain the release notes for any later releases.
     .. toctree::
        :maxdepth: 1
     
    +   1.8.15
        1.8.14
        1.8.13
        1.8.12
    
  • tests/httpwrappers/tests.py+51 1 modified
    @@ -21,7 +21,7 @@
     from django.test import TestCase
     from django.utils import six
     from django.utils._os import upath
    -from django.utils.encoding import force_text, smart_str
    +from django.utils.encoding import force_str, force_text, smart_str
     from django.utils.functional import lazy
     
     lazystr = lazy(force_text, six.text_type)
    @@ -643,6 +643,8 @@ def test_decode(self):
             c2 = SimpleCookie()
             c2.load(c.output()[12:])
             self.assertEqual(c['test'].value, c2['test'].value)
    +        c3 = parse_cookie(c.output()[12:])
    +        self.assertEqual(c['test'].value, c3['test'])
     
         def test_decode_2(self):
             """
    @@ -653,6 +655,8 @@ def test_decode_2(self):
             c2 = SimpleCookie()
             c2.load(c.output()[12:])
             self.assertEqual(c['test'].value, c2['test'].value)
    +        c3 = parse_cookie(c.output()[12:])
    +        self.assertEqual(c['test'].value, c3['test'])
     
         def test_nonstandard_keys(self):
             """
    @@ -666,6 +670,52 @@ def test_repeated_nonstandard_keys(self):
             """
             self.assertIn('good_cookie', parse_cookie('a:=b; a:=c; good_cookie=yes').keys())
     
    +    def test_python_cookies(self):
    +        """
    +        Test cases copied from Python's Lib/test/test_http_cookies.py
    +        """
    +        self.assertEqual(parse_cookie('chips=ahoy; vienna=finger'), {'chips': 'ahoy', 'vienna': 'finger'})
    +        # Here parse_cookie() differs from Python's cookie parsing in that it
    +        # treats all semicolons as delimiters, even within quotes.
    +        self.assertEqual(
    +            parse_cookie('keebler="E=mc2; L=\\"Loves\\"; fudge=\\012;"'),
    +            {'keebler': '"E=mc2', 'L': '\\"Loves\\"', 'fudge': '\\012', '': '"'}
    +        )
    +        # Illegal cookies that have an '=' char in an unquoted value.
    +        self.assertEqual(parse_cookie('keebler=E=mc2'), {'keebler': 'E=mc2'})
    +        # Cookies with ':' character in their name.
    +        self.assertEqual(parse_cookie('key:term=value:term'), {'key:term': 'value:term'})
    +        # Cookies with '[' and ']'.
    +        self.assertEqual(parse_cookie('a=b; c=[; d=r; f=h'), {'a': 'b', 'c': '[', 'd': 'r', 'f': 'h'})
    +
    +    def test_cookie_edgecases(self):
    +        # Cookies that RFC6265 allows.
    +        self.assertEqual(parse_cookie('a=b; Domain=example.com'), {'a': 'b', 'Domain': 'example.com'})
    +        # parse_cookie() has historically kept only the last cookie with the
    +        # same name.
    +        self.assertEqual(parse_cookie('a=b; h=i; a=c'), {'a': 'c', 'h': 'i'})
    +
    +    def test_invalid_cookies(self):
    +        """
    +        Cookie strings that go against RFC6265 but browsers will send if set
    +        via document.cookie.
    +        """
    +        # Chunks without an equals sign appear as unnamed values per
    +        # https://bugzilla.mozilla.org/show_bug.cgi?id=169091
    +        self.assertIn('django_language', parse_cookie('abc=def; unnamed; django_language=en').keys())
    +        # Even a double quote may be an unamed value.
    +        self.assertEqual(parse_cookie('a=b; "; c=d'), {'a': 'b', '': '"', 'c': 'd'})
    +        # Spaces in names and values, and an equals sign in values.
    +        self.assertEqual(parse_cookie('a b c=d e = f; gh=i'), {'a b c': 'd e = f', 'gh': 'i'})
    +        # More characters the spec forbids.
    +        self.assertEqual(parse_cookie('a   b,c<>@:/[]?{}=d  "  =e,f g'), {'a   b,c<>@:/[]?{}': 'd  "  =e,f g'})
    +        # Unicode characters. The spec only allows ASCII.
    +        self.assertEqual(parse_cookie('saint=André Bessette'), {'saint': force_str('André Bessette')})
    +        # Browsers don't send extra whitespace or semicolons in Cookie headers,
    +        # but parse_cookie() should parse whitespace the same way
    +        # document.cookie parses whitespace.
    +        self.assertEqual(parse_cookie('  =  b  ;  ;  =  ;   c  =  ;  '), {'': 'b', 'c': ''})
    +
         def test_httponly_after_load(self):
             """
             Test that we can use httponly attribute on cookies that we load
    
  • tests/requests/tests.py+1 4 modified
    @@ -10,7 +10,7 @@
     from django.core.handlers.wsgi import LimitedStream, WSGIRequest
     from django.http import (
         HttpRequest, HttpResponse, RawPostDataException, UnreadablePostError,
    -    build_request_repr, parse_cookie,
    +    build_request_repr,
     )
     from django.test import RequestFactory, SimpleTestCase, override_settings
     from django.test.client import FakePayload
    @@ -161,9 +161,6 @@ def wsgi_str(path_info):
             request = WSGIRequest({'PATH_INFO': wsgi_str("/سلام/"), 'REQUEST_METHOD': 'get', 'wsgi.input': BytesIO(b'')})
             self.assertEqual(request.path, "/سلام/")
     
    -    def test_parse_cookie(self):
    -        self.assertEqual(parse_cookie('invalid@key=true'), {})
    -
         def test_httprequest_location(self):
             request = HttpRequest()
             self.assertEqual(request.build_absolute_uri(location="https://www.example.com/asdf"),
    
d1bc980db1c0

[1.9.x] Fixed CVE-2016-7401 -- Fixed CSRF protection bypass on a site with Google Analytics.

https://github.com/django/djangoCollin AndersonMar 12, 2016via ghsa
6 files changed · +105 18
  • django/http/cookie.py+16 13 modified
    @@ -89,18 +89,21 @@ def _BaseCookie__set(self, key, real_value, coded_value):
     
     
     def parse_cookie(cookie):
    -    if cookie == '':
    -        return {}
    -    if not isinstance(cookie, http_cookies.BaseCookie):
    -        try:
    -            c = SimpleCookie()
    -            c.load(cookie)
    -        except http_cookies.CookieError:
    -            # Invalid cookie
    -            return {}
    -    else:
    -        c = cookie
    +    """
    +    Return a dictionary parsed from a `Cookie:` header string.
    +    """
         cookiedict = {}
    -    for key in c.keys():
    -        cookiedict[key] = c.get(key).value
    +    if six.PY2:
    +        cookie = force_str(cookie)
    +    for chunk in cookie.split(str(';')):
    +        if str('=') in chunk:
    +            key, val = chunk.split(str('='), 1)
    +        else:
    +            # Assume an empty name per
    +            # https://bugzilla.mozilla.org/show_bug.cgi?id=169091
    +            key, val = str(''), chunk
    +        key, val = key.strip(), val.strip()
    +        if key or val:
    +            # unquote using Python's algorithm.
    +            cookiedict[key] = http_cookies._unquote(val)
         return cookiedict
    
  • docs/releases/1.8.15.txt+18 0 added
    @@ -0,0 +1,18 @@
    +===========================
    +Django 1.8.15 release notes
    +===========================
    +
    +*September 26, 2016*
    +
    +Django 1.8.15 fixes a security issue in 1.8.14.
    +
    +CSRF protection bypass on a site with Google Analytics
    +======================================================
    +
    +An interaction between Google Analytics and Django's cookie parsing could allow
    +an attacker to set arbitrary cookies leading to a bypass of CSRF protection.
    +
    +The parser for ``request.COOKIES`` is simplified to better match the behavior
    +of browsers and to mitigate this attack. ``request.COOKIES`` may now contain
    +cookies that are invalid according to :rfc:`6265` but are possible to set via
    +``document.cookie``.
    
  • docs/releases/1.9.10.txt+18 0 added
    @@ -0,0 +1,18 @@
    +===========================
    +Django 1.9.10 release notes
    +===========================
    +
    +*September 26, 2016*
    +
    +Django 1.9.10 fixes a security issue in 1.9.9.
    +
    +CSRF protection bypass on a site with Google Analytics
    +======================================================
    +
    +An interaction between Google Analytics and Django's cookie parsing could allow
    +an attacker to set arbitrary cookies leading to a bypass of CSRF protection.
    +
    +The parser for ``request.COOKIES`` is simplified to better match the behavior
    +of browsers and to mitigate this attack. ``request.COOKIES`` may now contain
    +cookies that are invalid according to :rfc:`6265` but are possible to set via
    +``document.cookie``.
    
  • docs/releases/index.txt+2 0 modified
    @@ -25,6 +25,7 @@ versions of the documentation contain the release notes for any later releases.
     .. toctree::
        :maxdepth: 1
     
    +   1.9.10
        1.9.9
        1.9.8
        1.9.7
    @@ -41,6 +42,7 @@ versions of the documentation contain the release notes for any later releases.
     .. toctree::
        :maxdepth: 1
     
    +   1.8.15
        1.8.14
        1.8.13
        1.8.12
    
  • tests/httpwrappers/tests.py+51 1 modified
    @@ -21,7 +21,7 @@
     from django.test import SimpleTestCase
     from django.utils import six
     from django.utils._os import upath
    -from django.utils.encoding import force_text, smart_str
    +from django.utils.encoding import force_str, force_text, smart_str
     from django.utils.functional import lazy
     
     lazystr = lazy(force_text, six.text_type)
    @@ -657,6 +657,8 @@ def test_decode(self):
             c2 = SimpleCookie()
             c2.load(c.output()[12:])
             self.assertEqual(c['test'].value, c2['test'].value)
    +        c3 = parse_cookie(c.output()[12:])
    +        self.assertEqual(c['test'].value, c3['test'])
     
         def test_decode_2(self):
             """
    @@ -667,6 +669,8 @@ def test_decode_2(self):
             c2 = SimpleCookie()
             c2.load(c.output()[12:])
             self.assertEqual(c['test'].value, c2['test'].value)
    +        c3 = parse_cookie(c.output()[12:])
    +        self.assertEqual(c['test'].value, c3['test'])
     
         def test_nonstandard_keys(self):
             """
    @@ -680,6 +684,52 @@ def test_repeated_nonstandard_keys(self):
             """
             self.assertIn('good_cookie', parse_cookie('a:=b; a:=c; good_cookie=yes').keys())
     
    +    def test_python_cookies(self):
    +        """
    +        Test cases copied from Python's Lib/test/test_http_cookies.py
    +        """
    +        self.assertEqual(parse_cookie('chips=ahoy; vienna=finger'), {'chips': 'ahoy', 'vienna': 'finger'})
    +        # Here parse_cookie() differs from Python's cookie parsing in that it
    +        # treats all semicolons as delimiters, even within quotes.
    +        self.assertEqual(
    +            parse_cookie('keebler="E=mc2; L=\\"Loves\\"; fudge=\\012;"'),
    +            {'keebler': '"E=mc2', 'L': '\\"Loves\\"', 'fudge': '\\012', '': '"'}
    +        )
    +        # Illegal cookies that have an '=' char in an unquoted value.
    +        self.assertEqual(parse_cookie('keebler=E=mc2'), {'keebler': 'E=mc2'})
    +        # Cookies with ':' character in their name.
    +        self.assertEqual(parse_cookie('key:term=value:term'), {'key:term': 'value:term'})
    +        # Cookies with '[' and ']'.
    +        self.assertEqual(parse_cookie('a=b; c=[; d=r; f=h'), {'a': 'b', 'c': '[', 'd': 'r', 'f': 'h'})
    +
    +    def test_cookie_edgecases(self):
    +        # Cookies that RFC6265 allows.
    +        self.assertEqual(parse_cookie('a=b; Domain=example.com'), {'a': 'b', 'Domain': 'example.com'})
    +        # parse_cookie() has historically kept only the last cookie with the
    +        # same name.
    +        self.assertEqual(parse_cookie('a=b; h=i; a=c'), {'a': 'c', 'h': 'i'})
    +
    +    def test_invalid_cookies(self):
    +        """
    +        Cookie strings that go against RFC6265 but browsers will send if set
    +        via document.cookie.
    +        """
    +        # Chunks without an equals sign appear as unnamed values per
    +        # https://bugzilla.mozilla.org/show_bug.cgi?id=169091
    +        self.assertIn('django_language', parse_cookie('abc=def; unnamed; django_language=en').keys())
    +        # Even a double quote may be an unamed value.
    +        self.assertEqual(parse_cookie('a=b; "; c=d'), {'a': 'b', '': '"', 'c': 'd'})
    +        # Spaces in names and values, and an equals sign in values.
    +        self.assertEqual(parse_cookie('a b c=d e = f; gh=i'), {'a b c': 'd e = f', 'gh': 'i'})
    +        # More characters the spec forbids.
    +        self.assertEqual(parse_cookie('a   b,c<>@:/[]?{}=d  "  =e,f g'), {'a   b,c<>@:/[]?{}': 'd  "  =e,f g'})
    +        # Unicode characters. The spec only allows ASCII.
    +        self.assertEqual(parse_cookie('saint=André Bessette'), {'saint': force_str('André Bessette')})
    +        # Browsers don't send extra whitespace or semicolons in Cookie headers,
    +        # but parse_cookie() should parse whitespace the same way
    +        # document.cookie parses whitespace.
    +        self.assertEqual(parse_cookie('  =  b  ;  ;  =  ;   c  =  ;  '), {'': 'b', 'c': ''})
    +
         def test_httponly_after_load(self):
             """
             Test that we can use httponly attribute on cookies that we load
    
  • tests/requests/tests.py+0 4 modified
    @@ -10,7 +10,6 @@
     from django.core.handlers.wsgi import LimitedStream, WSGIRequest
     from django.http import (
         HttpRequest, HttpResponse, RawPostDataException, UnreadablePostError,
    -    parse_cookie,
     )
     from django.test import RequestFactory, SimpleTestCase, override_settings
     from django.test.client import FakePayload
    @@ -154,9 +153,6 @@ def wsgi_str(path_info):
             request = WSGIRequest({'PATH_INFO': wsgi_str("/سلام/"), 'REQUEST_METHOD': 'get', 'wsgi.input': BytesIO(b'')})
             self.assertEqual(request.path, "/سلام/")
     
    -    def test_parse_cookie(self):
    -        self.assertEqual(parse_cookie('invalid@key=true'), {})
    -
         def test_httprequest_location(self):
             request = HttpRequest()
             self.assertEqual(request.build_absolute_uri(location="https://www.example.com/asdf"),
    

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

20

News mentions

0

No linked articles in our index yet.