VYPR
High severityNVD Advisory· Published Nov 22, 2024· Updated Nov 3, 2025

Tornado has HTTP cookie parsing DoS vulnerability

CVE-2024-52804

Description

Tornado is a Python web framework and asynchronous networking library. The algorithm used for parsing HTTP cookies in Tornado versions prior to 6.4.2 sometimes has quadratic complexity, leading to excessive CPU consumption when parsing maliciously-crafted cookie headers. This parsing occurs in the event loop thread and may block the processing of other requests. Version 6.4.2 fixes the issue.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
tornadoPyPI
< 6.4.26.4.2

Affected products

1

Patches

1
d5ba4a1695fb

httputil: Fix quadratic performance of cookie parsing

https://github.com/tornadoweb/tornadoBen DarnellNov 21, 2024via ghsa
2 files changed · +56 28
  • tornado/httputil.py+10 28 modified
    @@ -1057,15 +1057,20 @@ def qs_to_qsl(qs: Dict[str, List[AnyStr]]) -> Iterable[Tuple[str, AnyStr]]:
                 yield (k, v)
     
     
    -_OctalPatt = re.compile(r"\\[0-3][0-7][0-7]")
    -_QuotePatt = re.compile(r"[\\].")
    -_nulljoin = "".join
    +_unquote_sub = re.compile(r"\\(?:([0-3][0-7][0-7])|(.))").sub
    +
    +
    +def _unquote_replace(m: re.Match) -> str:
    +    if m[1]:
    +        return chr(int(m[1], 8))
    +    else:
    +        return m[2]
     
     
     def _unquote_cookie(s: str) -> str:
         """Handle double quotes and escaping in cookie values.
     
    -    This method is copied verbatim from the Python 3.5 standard
    +    This method is copied verbatim from the Python 3.13 standard
         library (http.cookies._unquote) so we don't have to depend on
         non-public interfaces.
         """
    @@ -1086,30 +1091,7 @@ def _unquote_cookie(s: str) -> str:
         #    \012 --> \n
         #    \"   --> "
         #
    -    i = 0
    -    n = len(s)
    -    res = []
    -    while 0 <= i < n:
    -        o_match = _OctalPatt.search(s, i)
    -        q_match = _QuotePatt.search(s, i)
    -        if not o_match and not q_match:  # Neither matched
    -            res.append(s[i:])
    -            break
    -        # else:
    -        j = k = -1
    -        if o_match:
    -            j = o_match.start(0)
    -        if q_match:
    -            k = q_match.start(0)
    -        if q_match and (not o_match or k < j):  # QuotePatt matched
    -            res.append(s[i:k])
    -            res.append(s[k + 1])
    -            i = k + 2
    -        else:  # OctalPatt matched
    -            res.append(s[i:j])
    -            res.append(chr(int(s[j + 1 : j + 4], 8)))
    -            i = j + 4
    -    return _nulljoin(res)
    +    return _unquote_sub(_unquote_replace, s)
     
     
     def parse_cookie(cookie: str) -> Dict[str, str]:
    
  • tornado/test/httputil_test.py+46 0 modified
    @@ -560,3 +560,49 @@ def test_invalid_cookies(self):
             self.assertEqual(
                 parse_cookie("  =  b  ;  ;  =  ;   c  =  ;  "), {"": "b", "c": ""}
             )
    +
    +    def test_unquote(self):
    +        # Copied from
    +        # https://github.com/python/cpython/blob/dc7a2b6522ec7af41282bc34f405bee9b306d611/Lib/test/test_http_cookies.py#L62
    +        cases = [
    +            (r'a="b=\""', 'b="'),
    +            (r'a="b=\\"', "b=\\"),
    +            (r'a="b=\="', "b=="),
    +            (r'a="b=\n"', "b=n"),
    +            (r'a="b=\042"', 'b="'),
    +            (r'a="b=\134"', "b=\\"),
    +            (r'a="b=\377"', "b=\xff"),
    +            (r'a="b=\400"', "b=400"),
    +            (r'a="b=\42"', "b=42"),
    +            (r'a="b=\\042"', "b=\\042"),
    +            (r'a="b=\\134"', "b=\\134"),
    +            (r'a="b=\\\""', 'b=\\"'),
    +            (r'a="b=\\\042"', 'b=\\"'),
    +            (r'a="b=\134\""', 'b=\\"'),
    +            (r'a="b=\134\042"', 'b=\\"'),
    +        ]
    +        for encoded, decoded in cases:
    +            with self.subTest(encoded):
    +                c = parse_cookie(encoded)
    +                self.assertEqual(c["a"], decoded)
    +
    +    def test_unquote_large(self):
    +        # Adapted from
    +        # https://github.com/python/cpython/blob/dc7a2b6522ec7af41282bc34f405bee9b306d611/Lib/test/test_http_cookies.py#L87
    +        # Modified from that test because we handle semicolons differently from the stdlib.
    +        #
    +        # This is a performance regression test: prior to improvements in Tornado 6.4.2, this test
    +        # would take over a minute with n= 100k. Now it runs in tens of milliseconds.
    +        n = 100000
    +        for encoded in r"\\", r"\134":
    +            with self.subTest(encoded):
    +                start = time.time()
    +                data = 'a="b=' + encoded * n + '"'
    +                value = parse_cookie(data)["a"]
    +                end = time.time()
    +                self.assertEqual(value[:3], "b=\\")
    +                self.assertEqual(value[-3:], "\\\\\\")
    +                self.assertEqual(len(value), n + 2)
    +
    +                # Very loose performance check to avoid false positives
    +                self.assertLess(end - start, 1, "Test took too long")
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.