Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 6118ab7

Browse files
collinandersontimgraham
authored andcommitted
[1.8.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." 93a135d from master
1 parent 717aa88 commit 6118ab7

File tree

5 files changed

+87
-18
lines changed

5 files changed

+87
-18
lines changed

django/http/cookie.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -89,18 +89,21 @@ def _BaseCookie__set(self, key, real_value, coded_value):
8989

9090

9191
def parse_cookie(cookie):
92-
if cookie == '':
93-
return {}
94-
if not isinstance(cookie, http_cookies.BaseCookie):
95-
try:
96-
c = SimpleCookie()
97-
c.load(cookie)
98-
except http_cookies.CookieError:
99-
# Invalid cookie
100-
return {}
101-
else:
102-
c = cookie
92+
"""
93+
Return a dictionary parsed from a `Cookie:` header string.
94+
"""
10395
cookiedict = {}
104-
for key in c.keys():
105-
cookiedict[key] = c.get(key).value
96+
if six.PY2:
97+
cookie = force_str(cookie)
98+
for chunk in cookie.split(str(';')):
99+
if str('=') in chunk:
100+
key, val = chunk.split(str('='), 1)
101+
else:
102+
# Assume an empty name per
103+
# https://bugzilla.mozilla.org/show_bug.cgi?id=169091
104+
key, val = str(''), chunk
105+
key, val = key.strip(), val.strip()
106+
if key or val:
107+
# unquote using Python's algorithm.
108+
cookiedict[key] = http_cookies._unquote(val)
106109
return cookiedict

docs/releases/1.8.15.txt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
===========================
2+
Django 1.8.15 release notes
3+
===========================
4+
5+
*September 26, 2016*
6+
7+
Django 1.8.15 fixes a security issue in 1.8.14.
8+
9+
CSRF protection bypass on a site with Google Analytics
10+
======================================================
11+
12+
An interaction between Google Analytics and Django's cookie parsing could allow
13+
an attacker to set arbitrary cookies leading to a bypass of CSRF protection.
14+
15+
The parser for ``request.COOKIES`` is simplified to better match the behavior
16+
of browsers and to mitigate this attack. ``request.COOKIES`` may now contain
17+
cookies that are invalid according to :rfc:`6265` but are possible to set via
18+
``document.cookie``.

docs/releases/index.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ versions of the documentation contain the release notes for any later releases.
2525
.. toctree::
2626
:maxdepth: 1
2727

28+
1.8.15
2829
1.8.14
2930
1.8.13
3031
1.8.12

tests/httpwrappers/tests.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from django.test import TestCase
2222
from django.utils import six
2323
from django.utils._os import upath
24-
from django.utils.encoding import force_text, smart_str
24+
from django.utils.encoding import force_str, force_text, smart_str
2525
from django.utils.functional import lazy
2626

2727
lazystr = lazy(force_text, six.text_type)
@@ -643,6 +643,8 @@ def test_decode(self):
643643
c2 = SimpleCookie()
644644
c2.load(c.output()[12:])
645645
self.assertEqual(c['test'].value, c2['test'].value)
646+
c3 = parse_cookie(c.output()[12:])
647+
self.assertEqual(c['test'].value, c3['test'])
646648

647649
def test_decode_2(self):
648650
"""
@@ -653,6 +655,8 @@ def test_decode_2(self):
653655
c2 = SimpleCookie()
654656
c2.load(c.output()[12:])
655657
self.assertEqual(c['test'].value, c2['test'].value)
658+
c3 = parse_cookie(c.output()[12:])
659+
self.assertEqual(c['test'].value, c3['test'])
656660

657661
def test_nonstandard_keys(self):
658662
"""
@@ -666,6 +670,52 @@ def test_repeated_nonstandard_keys(self):
666670
"""
667671
self.assertIn('good_cookie', parse_cookie('a:=b; a:=c; good_cookie=yes').keys())
668672

673+
def test_python_cookies(self):
674+
"""
675+
Test cases copied from Python's Lib/test/test_http_cookies.py
676+
"""
677+
self.assertEqual(parse_cookie('chips=ahoy; vienna=finger'), {'chips': 'ahoy', 'vienna': 'finger'})
678+
# Here parse_cookie() differs from Python's cookie parsing in that it
679+
# treats all semicolons as delimiters, even within quotes.
680+
self.assertEqual(
681+
parse_cookie('keebler="E=mc2; L=\\"Loves\\"; fudge=\\012;"'),
682+
{'keebler': '"E=mc2', 'L': '\\"Loves\\"', 'fudge': '\\012', '': '"'}
683+
)
684+
# Illegal cookies that have an '=' char in an unquoted value.
685+
self.assertEqual(parse_cookie('keebler=E=mc2'), {'keebler': 'E=mc2'})
686+
# Cookies with ':' character in their name.
687+
self.assertEqual(parse_cookie('key:term=value:term'), {'key:term': 'value:term'})
688+
# Cookies with '[' and ']'.
689+
self.assertEqual(parse_cookie('a=b; c=[; d=r; f=h'), {'a': 'b', 'c': '[', 'd': 'r', 'f': 'h'})
690+
691+
def test_cookie_edgecases(self):
692+
# Cookies that RFC6265 allows.
693+
self.assertEqual(parse_cookie('a=b; Domain=example.com'), {'a': 'b', 'Domain': 'example.com'})
694+
# parse_cookie() has historically kept only the last cookie with the
695+
# same name.
696+
self.assertEqual(parse_cookie('a=b; h=i; a=c'), {'a': 'c', 'h': 'i'})
697+
698+
def test_invalid_cookies(self):
699+
"""
700+
Cookie strings that go against RFC6265 but browsers will send if set
701+
via document.cookie.
702+
"""
703+
# Chunks without an equals sign appear as unnamed values per
704+
# https://bugzilla.mozilla.org/show_bug.cgi?id=169091
705+
self.assertIn('django_language', parse_cookie('abc=def; unnamed; django_language=en').keys())
706+
# Even a double quote may be an unamed value.
707+
self.assertEqual(parse_cookie('a=b; "; c=d'), {'a': 'b', '': '"', 'c': 'd'})
708+
# Spaces in names and values, and an equals sign in values.
709+
self.assertEqual(parse_cookie('a b c=d e = f; gh=i'), {'a b c': 'd e = f', 'gh': 'i'})
710+
# More characters the spec forbids.
711+
self.assertEqual(parse_cookie('a b,c<>@:/[]?{}=d " =e,f g'), {'a b,c<>@:/[]?{}': 'd " =e,f g'})
712+
# Unicode characters. The spec only allows ASCII.
713+
self.assertEqual(parse_cookie('saint=André Bessette'), {'saint': force_str('André Bessette')})
714+
# Browsers don't send extra whitespace or semicolons in Cookie headers,
715+
# but parse_cookie() should parse whitespace the same way
716+
# document.cookie parses whitespace.
717+
self.assertEqual(parse_cookie(' = b ; ; = ; c = ; '), {'': 'b', 'c': ''})
718+
669719
def test_httponly_after_load(self):
670720
"""
671721
Test that we can use httponly attribute on cookies that we load

tests/requests/tests.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from django.core.handlers.wsgi import LimitedStream, WSGIRequest
1111
from django.http import (
1212
HttpRequest, HttpResponse, RawPostDataException, UnreadablePostError,
13-
build_request_repr, parse_cookie,
13+
build_request_repr,
1414
)
1515
from django.test import RequestFactory, SimpleTestCase, override_settings
1616
from django.test.client import FakePayload
@@ -161,9 +161,6 @@ def wsgi_str(path_info):
161161
request = WSGIRequest({'PATH_INFO': wsgi_str("/سلام/"), 'REQUEST_METHOD': 'get', 'wsgi.input': BytesIO(b'')})
162162
self.assertEqual(request.path, "/سلام/")
163163

164-
def test_parse_cookie(self):
165-
self.assertEqual(parse_cookie('invalid@key=true'), {})
166-
167164
def test_httprequest_location(self):
168165
request = HttpRequest()
169166
self.assertEqual(request.build_absolute_uri(location="https://www.example.com/asdf"),

0 commit comments

Comments
 (0)