File: 0008-1.8.x-Fixed-CVE-2016-7401-Fixed-CSRF-protection-bypa.patch

package info (click to toggle)
python-django 1.7.11-1%2Bdeb8u3
  • links: PTS, VCS
  • area: main
  • in suites: jessie
  • size: 45,624 kB
  • sloc: python: 171,189; xml: 713; sh: 203; makefile: 199; sql: 11
file content (154 lines) | stat: -rw-r--r-- 7,018 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
From f29171ded2147ba6935566741b1e993c7e848595 Mon Sep 17 00:00:00 2001
From: Collin Anderson <cmawebsite@gmail.com>
Date: Fri, 11 Mar 2016 21:36:08 -0500
Subject: [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." 93a135d111c2569d88d65a3f4ad9e6d9ad291452 from master
---
 django/http/cookie.py       | 29 ++++++++++++++------------
 tests/httpwrappers/tests.py | 50 ++++++++++++++++++++++++++++++++++++++++++++-
 tests/requests/tests.py     |  5 +----
 3 files changed, 66 insertions(+), 18 deletions(-)

diff --git a/django/http/cookie.py b/django/http/cookie.py
index 7084c87..07d8cbf 100644
--- a/django/http/cookie.py
+++ b/django/http/cookie.py
@@ -71,18 +71,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
diff --git a/tests/httpwrappers/tests.py b/tests/httpwrappers/tests.py
index 7881f2f..25a9127 100644
--- a/tests/httpwrappers/tests.py
+++ b/tests/httpwrappers/tests.py
@@ -19,7 +19,7 @@ from django.http import (QueryDict, HttpResponse, HttpResponseRedirect,
                          parse_cookie)
 from django.test import TestCase
 from django.utils.deprecation import RemovedInDjango18Warning
-from django.utils.encoding import smart_str, force_text
+from django.utils.encoding import smart_str, force_text, force_str
 from django.utils.functional import lazy
 from django.utils._os import upath
 from django.utils import six
@@ -632,6 +632,8 @@ class CookieTests(unittest.TestCase):
         c2 = SimpleCookie()
         c2.load(c.output())
         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):
         """
@@ -645,6 +647,52 @@ class CookieTests(unittest.TestCase):
         """
         self.assertTrue('good_cookie' in 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
diff --git a/tests/requests/tests.py b/tests/requests/tests.py
index 55b37bb..6364ab1 100644
--- a/tests/requests/tests.py
+++ b/tests/requests/tests.py
@@ -11,7 +11,7 @@ from django.db import connection, connections
 from django.core import signals
 from django.core.exceptions import SuspiciousOperation
 from django.core.handlers.wsgi import WSGIRequest, LimitedStream
-from django.http import (HttpRequest, HttpResponse, parse_cookie,
+from django.http import (HttpRequest, HttpResponse,
     build_request_repr, UnreadablePostError, RawPostDataException)
 from django.test import SimpleTestCase, TransactionTestCase, override_settings
 from django.test.client import FakePayload
@@ -128,9 +128,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"),