Skip to main content
CVE Vulnerability Database
Vulnerability Database/CVE-2026-82397

CVE-2026-82397: Tornado Python Framework DOS Vulnerability

CVE-2026-82397 is a denial of service vulnerability in Tornado Python web framework that allows attackers to stall the event loop with malicious request bodies. This post explains its impact, affected versions, and mitigation steps.

Published:

CVE-2026-82397 Overview

CVE-2026-82397 is a denial-of-service vulnerability in the Tornado Python web framework and asynchronous networking library. Versions prior to 6.5.8 parse application/x-www-form-urlencoded request bodies using urllib.parse.parse_qs in tornado/escape.py without enforcing a max_num_fields limit. An unauthenticated attacker can submit a single request body containing millions of separator-delimited fields, stalling the single-threaded event loop and blocking all concurrent connections. The body size is bounded only by max_buffer_size, which defaults to 104857600 bytes (100 MB). The weakness is classified as [CWE-400] Uncontrolled Resource Consumption.

Critical Impact

A single unauthenticated HTTP request can synchronously freeze the Tornado event loop, delaying every connection served by the process and causing application-wide denial of service.

Affected Products

  • Tornado web framework versions prior to 6.5.8
  • Python applications built on Tornado's HTTPServerRequest body parsing
  • Any deployment exposing form-urlencoded endpoints without an upstream body-size or field-count filter

Discovery Timeline

Technical Details for CVE-2026-82397

Vulnerability Analysis

Tornado is a single-threaded asynchronous framework. All I/O and request dispatch run on one event loop per process. When a request arrives with Content-Type: application/x-www-form-urlencoded, RequestHandler._execute in tornado/web.py invokes HTTPServerRequest._parse_body, which calls parse_body_arguments in tornado/httputil.py before handler dispatch. That path forwards the raw body to urllib.parse.parse_qs in tornado/escape.py without setting max_num_fields. Parsing scales linearly with the number of &-separated fields, so a body near the default 100 MB max_buffer_size can contain tens of millions of empty fields. Each parse blocks the event loop synchronously, delaying every other connection handled by that process. Because the parsing happens before authentication or route logic, no credentials are required to trigger it.

Root Cause

The root cause is a missing upper bound on the number of fields Tornado accepts in a urlencoded body. Python's urllib.parse.parse_qs supports a max_num_fields keyword argument that raises ValueError when exceeded, but Tornado did not pass it. The only limit was the byte-level max_buffer_size, which is inadequate against payloads composed of short separator tokens.

Attack Vector

An attacker sends a single POST request with Content-Type: application/x-www-form-urlencoded and a body consisting of millions of empty key-value pairs (for example, &&&...) sized just under max_buffer_size. Tornado buffers the body, then blocks on parse_qs before dispatching to a handler. The event loop stalls for seconds to minutes, denying service to all clients of that worker process.

python
# Security patch in tornado/escape.py - adds max_num_fields enforcement
def parse_qs_bytes(
    qs: Union[str, bytes],
    keep_blank_values: bool = False,
    strict_parsing: bool = False,
    *,
    max_num_fields: Optional[int] = None,
) -> Dict[str, List[bytes]]:
    """Parses a query string like urlparse.parse_qs,
    but takes bytes and returns the values as byte strings.

    .. versionadded:: 6.5.8
       The ``max_num_fields`` argument. ValueError is raised if this limit is exceeded.
    """
    if isinstance(qs, bytes):
        qs = qs.decode("latin1")
    result = urllib.parse.parse_qs(
        qs,
        keep_blank_values,
        strict_parsing,
        encoding="latin1",
        errors="strict",
        max_num_fields=max_num_fields,
    )

Source: GitHub Tornado Commit 8d6363e

The companion change in tornado/httputil.py introduces a ParseUrlEncodedConfig dataclass with a default max_arguments = 1000, enforced on every urlencoded request body. See the Tornado Pull Request #3704 for the full patch set.

Detection Methods for CVE-2026-82397

Indicators of Compromise

  • POST requests to Tornado endpoints with Content-Type: application/x-www-form-urlencoded and unusually large Content-Length values approaching 100 MB.
  • Request bodies dominated by & separators with few or no key names, indicating field-count amplification.
  • Sustained high CPU on Tornado worker processes with the event loop stalled and pending connections queuing.

Detection Strategies

  • Log and alert on urlencoded POST bodies exceeding a small argument-count threshold (for example, more than 1000 fields) at the reverse proxy or WAF.
  • Correlate spikes in Tornado handler latency with inbound requests carrying oversized form payloads.
  • Monitor Python process metrics for event-loop lag using asyncio debug hooks or APM instrumentation.

Monitoring Recommendations

  • Instrument Tornado servers with request duration histograms and alert on p99 latency regressions tied to _parse_body.
  • Capture NetFlow or reverse-proxy access logs to identify single source IPs sending very large POST bodies to form endpoints.
  • Track connection queue depth on load balancers fronting Tornado applications to spot worker stalls early.

How to Mitigate CVE-2026-82397

Immediate Actions Required

  • Upgrade Tornado to version 6.5.8 or later across all Python services using pip install --upgrade tornado.
  • Inventory internal applications and dependencies that pin an older Tornado release and rebuild affected container images.
  • Restart Tornado worker processes after upgrading to ensure the patched code path is loaded.

Patch Information

The fix ships in Tornado 6.5.8. It adds a max_num_fields parameter to parse_qs_bytes and introduces ParseUrlEncodedConfig with a default max_arguments = 1000 enforced during body parsing. Reference the GHSA-mpf4-983q-p7j4 advisory and commit 8d6363e for the authoritative patch.

Workarounds

  • Lower max_buffer_size on the HTTPServer constructor to a value appropriate for expected form payloads, reducing the maximum field count attackers can pack into one request.
  • Place a reverse proxy such as nginx or a WAF in front of Tornado and enforce a strict client_max_body_size and request-argument limit.
  • Reject or rate-limit application/x-www-form-urlencoded requests on endpoints that only require JSON or multipart uploads.
bash
# Example nginx limit in front of Tornado to reduce field-count exposure
http {
    client_max_body_size 1m;
    client_body_buffer_size 16k;

    server {
        location / {
            limit_req zone=api burst=20 nodelay;
            proxy_pass http://tornado_upstream;
        }
    }
}

Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

Default Legacy - Prefooter | Experience the World’s Most Advanced Cybersecurity Platform

Experience the Most Advanced Cybersecurity Platform

See how the world’s most intelligent, autonomous cybersecurity platform can protect your organization today and into the future.