GHSA-mpf4-983q-p7j4: High severity pip/tornado vulnerability
Summary
Tornado parses application/x-www-form-urlencoded bodies with urllib.parse.parseqs and does not pass maxnumfields. A body made almost entirely of separators produces tens of millions of fields, and the parse happens on the event loop before the handler runs, so a single request stalls the whole server.
Where it is
tornado/escape.py, at HEAD e530031405e2154654dedc4c84d5656b557ea310:
python result = urllib.parse.parseqs( qs, keepblankvalues, strictparsing, encoding="latin1", errors="strict" )
maxnumfields is the parameter CPython added for exactly this, and it is absent.
The path to it is entirely server-side and pre-dispatch. RequestHandler.execute parses the body at tornado/web.py:1821, which reaches HTTPServerRequest.parsebody at tornado/httputil.py:636, and the urlencoded branch of parsebodyarguments calls parseqsbytes at tornado/httputil.py:1030.
The size that reaches it is bounded only by the body cap, which defaults to the stream's maxbuffersize of 104857600 at tornado/iostream.py:239, applied as the request body default at tornado/http1connection.py:136-140. A 100 MB body of separators is around fifty million fields.
Impact
Denial of service against the whole process, not one request. Tornado is single-threaded and the parse is synchronous on the event loop, so every other connection waits. No authentication is needed if any route accepts a form post, which is the normal case.
Suggested fix
Pass a bound:
python result = urllib.parse.parseqs( qs, keepblankvalues, strictparsing, encoding="latin1", errors="strict", maxnumfields=maxnumfields, )
with a conservative default and a way for applications to raise it. CPython raises ValueError when the limit is exceeded, which maps cleanly onto a 400.
Lowering the default body cap for urlencoded specifically would help too, since 100 MB of form fields is not a shape any real client sends.
Why I do not think this is a duplicate
The published tornado advisories cover out-of-bounds access in the C extension, unbounded accumulation of decompressed chunks in AsyncHTTPClient, the Authorization header surviving cross-origin redirects, credential leakage on curl handle reuse, and cookie attribute validation. The decompression one is the nearest in spirit and is on the client side; this is the server parsing a request body. The call is unchanged at HEAD.
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
pip/tornadoto a version that resolves this vulnerability.Fixed in 6.5.8
Event History
Frequently Asked Questions
Which deployments are exposed by default?
Tornado's default request-body limit is the stream max_buffer_size of 104857600 bytes (100 MB). Deployments that accept application/x-www-form-urlencoded request bodies can reach the vulnerable parsing path before request handlers run.
What does an attacker need to exploit this?
An attacker only needs network access to send a crafted application/x-www-form-urlencoded request body containing a very large number of separators. The parsing occurs before handler dispatch, so application-level handler checks do not prevent the server-side stall.
What can be done if an update cannot be applied immediately?
Reduce the permitted request-body size from the default 100 MB where feasible, since the input reaching the parser is bounded by that body cap. This reduces the maximum number of fields a separator-heavy body can create, though it does not remove the parsing issue.