Sep 2, 2026·6 min read·1 visit
Unauthenticated remote Denial of Service in Tornado's multipart parser due to memory and CPU amplification occurring before verification of max_parts limits, leading to Out-of-Memory crashes.
GHSA-8423-8FGW-73VQ is a pre-authentication denial of service vulnerability in the Tornado web server's handling of multipart/form-data. The flaw allows an unauthenticated remote attacker to cause memory exhaustion and CPU starvation by transmitting a crafted HTTP request containing a high density of boundary delimiters. Because Tornado splits the entire request body in memory prior to enforcing the max_parts validation threshold, the Python interpreter attempts to materialize a massive list of byte segments. This triggers an immediate memory exhaustion (OOM) crash or server-wide CPU starvation before the payload can be validated and rejected.
The Tornado web framework utilizes the parse_multipart_form_data function inside its httputil module to process incoming multipart/form-data payloads. This function is exposed across any endpoint accepting file uploads or complex multi-part forms. The primary task of this parser is to segment the raw HTTP request body into discrete, manageable blocks using a boundary delimiter string supplied in the Content-Type header.
To safeguard against resource exhaustion attacks, Tornado permits administrators to enforce a max_parts constraint, which defaults to 1000. Under normal conditions, requests exceeding this threshold are immediately rejected. However, a structural control flow issue exists because the parser completes the partitioning of the entire input buffer before verifying the number of resulting segments against the configured limit.
An attacker can exploit this operational sequence by crafting a payload containing a extremely dense distribution of boundary delimiters. By utilizing a single-character boundary string, the attacker forces the underlying Python engine to execute a complete buffer split and allocate millions of individual transient byte slices. This allocation occurs in the pre-validation phase, consuming all available system memory and causing process termination.
The underlying technical flaw lies in the algorithmic execution order of tornado.httputil.parse_multipart_form_data. When a client initiates a multipart request, Tornado reads the raw byte stream and searches for the terminal boundary indicator using a reverse search (rfind). Once identified, the engine invokes Python's native bytes.split() method to partition the input stream.
Python's built-in bytes.split() executes a linear scan of the entire target memory buffer to locate all instances of the specified delimiter. For every match found, CPython allocates a new byte slice object and appends its reference to a dynamically growing list. This intensive memory allocation phase occurs entirely within the runtime environment before control returns to Tornado's validation logic.
Because the list is fully materialized in-memory before Tornado checks the number of components, the protection mechanism is bypassed during the critical allocation phase. A small 600KB request body containing 100,000 artificial boundaries will force the system to instantiate 100,000 distinct list items. If scaled to a larger payload such as 100MB, the system attempts to process roughly 17.5 million list elements, leading to a complete depletion of system RAM.
The vulnerable logic is located within tornado/httputil.py. In affected versions, the parsing sequence was structured as follows:
# Vulnerable code path in affected versions of tornado/httputil.py
def parse_multipart_form_data(
boundary: bytes, data: bytes, arguments: Dict[str, List[bytes]], files: Dict[str, List[ObjectDict]], config: Optional[HttpRequestConfig] = None
) -> None:
...
final_boundary_index = data.rfind(b"--" + boundary + b"--")
if final_boundary_index == -1:
raise HTTPInputError("Invalid multipart/form-data: no final boundary found")
# Vulnerable split operation: processes entire stream with no limit
parts = data[:final_boundary_index].split(b"--" + boundary + b"\r\n")
# Limit validation: occurs after memory has already been exhausted
if len(parts) > config.max_parts:
raise HTTPInputError("multipart/form-data has too many parts")The vulnerability was resolved in commit de85b3f87446e323e881bbaa3d5a74f4b76e5f05 by passing an explicit limit to the split() function:
# Patched code path in tornado/httputil.py
# The split depth is restricted to config.max_parts + 1
parts = data[:final_boundary_index].split(
b"--" + boundary + b"\r\n", config.max_parts + 1
)By specifying config.max_parts + 1 as the maxsplit parameter, the Python interpreter halts the buffer scan and terminates partitioning immediately after reaching the limit. The remaining unparsed segment is left intact as the final entry in the list, ensuring that total list allocations never exceed config.max_parts + 2 elements. This hard ceiling prevents uncontrolled heap consumption.
Exploitation of GHSA-8423-8FGW-73VQ requires no authentication and can be completed through a single crafted HTTP POST request. The attacker targets any application route configured to handle multipart form submissions. To maximize amplification efficiency, the attacker overrides the multipart boundary parameter in the Content-Type header to a single character, such as x.
The attacker then generates a payload containing a dense sequence of delimiter bytes. For a boundary designated as x, the required delimiter search pattern is b"--" + b"x" + b"\r\n". A single arbitrary byte is positioned between each delimiter, creating a highly compressed payload:
POST /upload HTTP/1.1
Host: target-server.local
Content-Type: multipart/form-data; boundary=x
Content-Length: 600005
q--x
q--x
... [repeated 100,000 times] ...
--x--When the Tornado parser encounters this payload, the built-in split() method executes and instantly materializes 100,000 Python byte slice objects. Under concurrent conditions, or when scaled to larger request sizes, the rapid surge in memory allocations exhausts the host's physical RAM, forcing the operating system kernel's Out-of-Memory (OOM) killer to terminate the web server process.
The operational impact of this vulnerability is a complete Denial of Service (DoS) affecting web application availability. Because Tornado relies on an asynchronous, single-threaded event loop structure, blocking operations or process crashes immediately terminate service for all active and pending client connections.
While this issue does not facilitate remote code execution or expose sensitive application data, it represents a highly effective disruption vector. In containerized environments such as Docker or Kubernetes, the depletion of container memory limits will trigger automatic pod restarts, leading to a continuous state of instability if the attack is sustained.
No specialized exploitation tools or privileges are needed to launch this attack. The presence of public proof-of-concept scripts combined with the pre-authentication nature of the vulnerability creates a low barrier to entry for prospective threat actors seeking to degrade service availability.
The recommended remediation path is to upgrade Tornado to version 6.5.8 or higher. This version implements the restricted maxsplit logic, preventing memory amplification.
If immediate upgrading is not possible, security teams should implement perimeter defensive controls. Web Application Firewalls (WAFs) or reverse proxies (such as Nginx) can be configured to block requests that contain suspiciously short boundary strings in the Content-Type header. Standard browsers generate long, random boundary hashes, so rejecting boundaries shorter than 8 characters is a safe and reliable filter.
# Example Nginx configuration to restrict request body sizes on upload endpoints
client_max_body_size 2M;Additionally, restricting the maximum permitted body size for multipart requests limits the raw volume of delimiters an attacker can submit, effectively capping the potential amplification factor.
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
tornado tornadoweb | < 6.5.8 | 6.5.8 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-400 |
| Attack Vector | Network |
| CVSS Score | 5.3 |
| EPSS Score | Not Available |
| Impact | Denial of Service (DoS) |
| Exploit Status | PoC Available |
| KEV Status | Not Listed |
The program does not properly control the allocation and maintenance of a key resource, allowing an actor to construct a request designed to consume excessive resources.
An incomplete sanitization fix for CVE-2026-35536 in Tornado allowed cookie attribute injection. The framework's validation loop checked lowercase keyword arguments but neglected legacy case-insensitive parameters passed through arbitrary keyword arguments, which Python's underlying library parses case-insensitively.
The league/commonmark library is subject to multiple denial of service vulnerabilities. These stem from three independent algorithmic complexity weaknesses in Markdown parsing: regular expression backtracking, reference link normalization, and delimiter processing. Remote, unauthenticated attackers can exploit these flaws by submitting crafted Markdown input to exhaust CPU execution resources, leading to application-wide thread exhaustion.
An inconsistency in whitespace handling between the PCRE regex engine, PHP's native trim function, and web browsers allows unauthenticated attackers to bypass XSS protections in the league/commonmark AttributesExtension by injecting a Form Feed (U+000C) control character.
GHSA-JJV6-8J6V-6J52 details multiple algorithmic complexity issues in the SmartPunct and Attributes extensions of the league/commonmark PHP library, leading to high CPU consumption and Denial of Service (DoS) when parsing pathological Markdown inputs.
An Untrusted Search Path (CWE-426) vulnerability exists in the Natural Language Toolkit (NLTK) library when executing the Graphviz 'dot' utility. Because the library fails to enforce absolute paths when executing external commands, local attackers can plant a malicious binary named 'dot' inside the current working directory. The library then executes the malicious binary, resulting in local arbitrary code execution under the context of the running Python process.
An algorithmic complexity vulnerability (CWE-407) in the AttributesExtension of league/commonmark allows unauthenticated remote attackers to cause CPU exhaustion and Denial of Service (DoS) via crafted Markdown payloads containing adjacent or consecutive attributes.