[1.9.x] Fixed CVE-2016-7401 -- Fixed CSRF protection bypass on a site with Google Analytics.
This is a security fix.
Backport of "refs #26158 -- rewrote http.parse_cookie() to better match
browsers." 93a135d111
from master
This commit is contained in:
parent
07760d0714
commit
d1bc980db1
|
@ -89,18 +89,21 @@ else:
|
|||
|
||||
|
||||
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
|
||||
|
|
|
@ -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``.
|
|
@ -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``.
|
|
@ -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
|
||||
|
|
|
@ -21,7 +21,7 @@ from django.http import (
|
|||
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 @@ class CookieTests(unittest.TestCase):
|
|||
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 @@ class CookieTests(unittest.TestCase):
|
|||
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 @@ class CookieTests(unittest.TestCase):
|
|||
"""
|
||||
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
|
||||
|
|
|
@ -10,7 +10,6 @@ from django.core.exceptions import SuspiciousOperation
|
|||
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 @@ class RequestsTests(SimpleTestCase):
|
|||
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"),
|
||||
|
|
Loading…
Reference in New Issue