Skip to content

Commit 096104d

Browse files
committed
CVE-2024-7592 Fix quadratic complexity in parsing quoted cookie
pythongh-123067: Fix quadratic complexity in parsing "-quoted cookie … …values with backslashes (pythonGH-123075) This fixes CVE-2024-7592.
1 parent 91c319e commit 096104d

File tree

3 files changed

+47
-24
lines changed

3 files changed

+47
-24
lines changed

Lib/Cookie.py

+7-24
Original file line numberDiff line numberDiff line change
@@ -323,9 +323,13 @@ def _quote(str, LegalChars=_LegalChars,
323323
return '"' + _nulljoin( map(_Translator.get, str, str) ) + '"'
324324
# end _quote
325325

326+
_unquote_sub = re.compile(r'\\(?:([0-3][0-7][0-7])|(.))').sub
326327

327-
_OctalPatt = re.compile(r"\\[0-3][0-7][0-7]")
328-
_QuotePatt = re.compile(r"[\\].")
328+
def _unquote_replace(m):
329+
if m.group(1):
330+
return chr(int(m.group(1), 8))
331+
else:
332+
return m.group(2)
329333

330334
def _unquote(str):
331335
# If there aren't any doublequotes,
@@ -345,28 +349,7 @@ def _unquote(str):
345349
# \012 --> \n
346350
# \" --> "
347351
#
348-
i = 0
349-
n = len(str)
350-
res = []
351-
while 0 <= i < n:
352-
Omatch = _OctalPatt.search(str, i)
353-
Qmatch = _QuotePatt.search(str, i)
354-
if not Omatch and not Qmatch: # Neither matched
355-
res.append(str[i:])
356-
break
357-
# else:
358-
j = k = -1
359-
if Omatch: j = Omatch.start(0)
360-
if Qmatch: k = Qmatch.start(0)
361-
if Qmatch and ( not Omatch or k < j ): # QuotePatt matched
362-
res.append(str[i:k])
363-
res.append(str[k+1])
364-
i = k+2
365-
else: # OctalPatt matched
366-
res.append(str[i:j])
367-
res.append( chr( int(str[j+1:j+4], 8) ) )
368-
i = j+4
369-
return _nulljoin(res)
352+
return _unquote_sub(_unquote_replace, str)
370353
# end _unquote
371354

372355
# The _getdate() routine is used to set the expiration time in

Lib/test/test_cookie.py

+39
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Simple test suite for Cookie.py
22

33
from test.test_support import run_unittest, run_doctest, check_warnings
4+
from test import support
45
import unittest
56
import Cookie
67
import pickle
@@ -156,6 +157,44 @@ def test_invalid_cookies(self):
156157
self.assertEqual(dict(C), {})
157158
self.assertEqual(C.output(), '')
158159

160+
def test_unquote(self):
161+
cases = [
162+
(r'a="b=\""', 'b="'),
163+
(r'a="b=\\"', 'b=\\'),
164+
(r'a="b=\="', 'b=='),
165+
(r'a="b=\n"', 'b=n'),
166+
(r'a="b=\042"', 'b="'),
167+
(r'a="b=\134"', 'b=\\'),
168+
(r'a="b=\377"', 'b=\xff'),
169+
(r'a="b=\400"', 'b=400'),
170+
(r'a="b=\42"', 'b=42'),
171+
(r'a="b=\\042"', 'b=\\042'),
172+
(r'a="b=\\134"', 'b=\\134'),
173+
(r'a="b=\\\""', 'b=\\"'),
174+
(r'a="b=\\\042"', 'b=\\"'),
175+
(r'a="b=\134\""', 'b=\\"'),
176+
(r'a="b=\134\042"', 'b=\\"'),
177+
]
178+
for encoded, decoded in cases:
179+
# with self.subTest(encoded):
180+
C = Cookie.SimpleCookie()
181+
C.load(encoded)
182+
self.assertEqual(C['a'].value, decoded)
183+
184+
@support.requires_resource('cpu')
185+
def test_unquote_large(self):
186+
n = 10**6
187+
for encoded in r'\\', r'\134':
188+
# with self.subTest(encoded):
189+
data = 'a="b=' + encoded*n + ';"'
190+
C = Cookie.SimpleCookie()
191+
C.load(data)
192+
value = C['a'].value
193+
self.assertEqual(value[:3], 'b=\\')
194+
self.assertEqual(value[-2:], '\\;')
195+
self.assertEqual(len(value), n + 3)
196+
197+
159198
def test_pickle(self):
160199
rawdata = 'Customer="WILE_E_COYOTE"; Path=/acme; Version=1'
161200
expected_output = 'Set-Cookie: %s' % rawdata
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)