Skip to content

Commit 44e4583

Browse files
gh-123067: Fix quadratic complexity in parsing "-quoted cookie values with backslashes (GH-123075)
This fixes CVE-2024-7592.
1 parent d60b97a commit 44e4583

File tree

3 files changed

+47
-26
lines changed

3 files changed

+47
-26
lines changed

Lib/http/cookies.py

Lines changed: 8 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,13 @@ def _quote(str):
184184
return '"' + str.translate(_Translator) + '"'
185185

186186

187-
_OctalPatt = re.compile(r"\\[0-3][0-7][0-7]")
188-
_QuotePatt = re.compile(r"[\\].")
187+
_unquote_sub = re.compile(r'\\(?:([0-3][0-7][0-7])|(.))').sub
188+
189+
def _unquote_replace(m):
190+
if m[1]:
191+
return chr(int(m[1], 8))
192+
else:
193+
return m[2]
189194

190195
def _unquote(str):
191196
# If there aren't any doublequotes,
@@ -205,30 +210,7 @@ def _unquote(str):
205210
# \012 --> \n
206211
# \" --> "
207212
#
208-
i = 0
209-
n = len(str)
210-
res = []
211-
while 0 <= i < n:
212-
o_match = _OctalPatt.search(str, i)
213-
q_match = _QuotePatt.search(str, i)
214-
if not o_match and not q_match: # Neither matched
215-
res.append(str[i:])
216-
break
217-
# else:
218-
j = k = -1
219-
if o_match:
220-
j = o_match.start(0)
221-
if q_match:
222-
k = q_match.start(0)
223-
if q_match and (not o_match or k < j): # QuotePatt matched
224-
res.append(str[i:k])
225-
res.append(str[k+1])
226-
i = k + 2
227-
else: # OctalPatt matched
228-
res.append(str[i:j])
229-
res.append(chr(int(str[j+1:j+4], 8)))
230-
i = j + 4
231-
return _nulljoin(res)
213+
return _unquote_sub(_unquote_replace, str)
232214

233215
# The _getdate() routine is used to set the expiration time in the cookie's HTTP
234216
# header. By default, _getdate() returns the current time in the appropriate

Lib/test/test_http_cookies.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import doctest
66
from http import cookies
77
import pickle
8+
from test import support
89

910

1011
class CookieTests(unittest.TestCase):
@@ -58,6 +59,43 @@ def test_basic(self):
5859
for k, v in sorted(case['dict'].items()):
5960
self.assertEqual(C[k].value, v)
6061

62+
def test_unquote(self):
63+
cases = [
64+
(r'a="b=\""', 'b="'),
65+
(r'a="b=\\"', 'b=\\'),
66+
(r'a="b=\="', 'b=='),
67+
(r'a="b=\n"', 'b=n'),
68+
(r'a="b=\042"', 'b="'),
69+
(r'a="b=\134"', 'b=\\'),
70+
(r'a="b=\377"', 'b=\xff'),
71+
(r'a="b=\400"', 'b=400'),
72+
(r'a="b=\42"', 'b=42'),
73+
(r'a="b=\\042"', 'b=\\042'),
74+
(r'a="b=\\134"', 'b=\\134'),
75+
(r'a="b=\\\""', 'b=\\"'),
76+
(r'a="b=\\\042"', 'b=\\"'),
77+
(r'a="b=\134\""', 'b=\\"'),
78+
(r'a="b=\134\042"', 'b=\\"'),
79+
]
80+
for encoded, decoded in cases:
81+
with self.subTest(encoded):
82+
C = cookies.SimpleCookie()
83+
C.load(encoded)
84+
self.assertEqual(C['a'].value, decoded)
85+
86+
@support.requires_resource('cpu')
87+
def test_unquote_large(self):
88+
n = 10**6
89+
for encoded in r'\\', r'\134':
90+
with self.subTest(encoded):
91+
data = 'a="b=' + encoded*n + ';"'
92+
C = cookies.SimpleCookie()
93+
C.load(data)
94+
value = C['a'].value
95+
self.assertEqual(value[:3], 'b=\\')
96+
self.assertEqual(value[-2:], '\\;')
97+
self.assertEqual(len(value), n + 3)
98+
6199
def test_load(self):
62100
C = cookies.SimpleCookie()
63101
C.load('Customer="WILE_E_COYOTE"; Version=1; Path=/acme')
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix quadratic complexity in parsing ``"``-quoted cookie values with backslashes by :mod:`http.cookies`.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy