Sep 2, 2026·6 min read·16 visits
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.
A remote code execution vulnerability exists in Microsoft .NET due to untrusted data deserialization within the PresentationCore component. The flaw stems from implicit auto-conversion flags during clipboard data retrieval operations.
An out-of-bounds write vulnerability in Microsoft .NET Framework, .NET runtime, and Visual Studio allows unauthenticated remote attackers to execute arbitrary code via crafted pixel-shader bytecode or auto-converted clipboard payloads.
ASP.NET Core contains a thread deadlock vulnerability (CWE-833) that allows an unauthenticated remote attacker to cause a denial of service over the network by exhausting server thread pool resources.
CVE-2026-70035 is a high-severity Remote Code Execution (RCE) vulnerability in Microsoft .NET Framework, .NET runtime, and Visual Studio caused by improper handling during the deserialization of untrusted data (CWE-502). An unauthenticated attacker can execute arbitrary code within the context of the affected process by delivering a crafted serialized payload.
An elevation of privilege vulnerability exists in ASP.NET Core and Microsoft Visual Studio due to improper synchronization during concurrent thread execution. Authenticated remote attackers with low privileges can exploit a timing window to manipulate shared resources and assume higher-level execution contexts.
A path traversal vulnerability (CWE-22) in Contao CMS allows unauthenticated remote attackers to bypass directory boundary restrictions in ImagesController and access files within the project directory.