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

CVE-2026-103261: Tornado Web Framework DOS Vulnerability

CVE-2026-103261 is a denial of service vulnerability in Tornado web framework that allows attackers to stall the event loop through excessive query parameters. This post explains its impact, affected versions, and mitigation steps.

Published:

CVE-2026-103261 Overview

CVE-2026-103261 is a denial-of-service vulnerability in the Tornado Python web framework before version 6.5.9. The HTTPServerRequest.__init__ method fails to limit the number of query string fields parsed from incoming requests. Remote attackers can send unauthenticated HTTP GET requests containing thousands of query parameters to stall the event loop. Because Tornado uses a shared IOLoop, degraded response times affect all clients served by the same process. The flaw is categorized under [CWE-770: Allocation of Resources Without Limits or Throttling].

Critical Impact

Unauthenticated remote attackers can degrade or stall Tornado application response times by submitting GET requests with unbounded query-string field counts.

Affected Products

  • Tornado web framework versions prior to 6.5.9
  • Python applications and services built on vulnerable Tornado releases
  • Multi-tenant services sharing a single Tornado IOLoop

Discovery Timeline

  • 2026-10-01 - CVE-2026-103261 published to the National Vulnerability Database (NVD)
  • 2026-10-01 - Last updated in NVD database

Technical Details for CVE-2026-103261

Vulnerability Analysis

The vulnerability resides in Tornado's HTTP request parsing path. When HTTPServerRequest.__init__ processes an incoming request URI, it calls parse_qs_bytes on the query string without enforcing a maximum field count. Although the overall query string length is bounded by max_header_size, per-field parsing cost scales with the number of parameters. An attacker who submits a request with thousands of query parameters forces the event loop to spend significant CPU time inside synchronous parsing code. Because Tornado's asynchronous model relies on a single IOLoop to multiplex connections, blocking inside that loop stalls every other request handled by the same process.

Root Cause

The root cause is missing input bounding in query-string parsing. parse_qs_bytes accepted the full set of fields extracted from the URI, with no max_num_fields argument supplied, enabling algorithmic resource exhaustion at the application layer.

Attack Vector

Exploitation requires only network reachability to the Tornado service. No authentication or user interaction is required. An attacker issues HTTP GET requests whose URIs contain a very large number of key=value pairs. Each request consumes disproportionate parsing time, degrading availability for concurrent clients.

python
# Patch from tornado/httputil.py - commit 03945136ea9746eccf61caf88edae39642e59c93
        if uri is not None:
            self.path, sep, self.query = uri.partition("?")
-       self.arguments = parse_qs_bytes(self.query, keep_blank_values=True)
+       try:
+           self.arguments = parse_qs_bytes(
+               self.query,
+               keep_blank_values=True,
+               # The query string is bounded by max_header_size, but parsing
+               # is expensive enough per field to be worth limiting. Use the
+               # same limit as a urlencoded body. This reads the global
+               # config because HTTPServerRequest has no access to the
+               # per-connection configuration; see set_parse_body_config.
+               max_num_fields=_DEFAULT_PARSE_BODY_CONFIG.urlencoded.max_arguments,
+           )
+       except Exception as e:
+           raise HTTPInputError("Invalid query string: %s" % e) from e
        self.query_arguments = copy.deepcopy(self.arguments)
        self.body_arguments = {}  # type: Dict[str, List[bytes]]

Source: Tornado security patch commit 0394513. The fix applies the same max_num_fields limit used for urlencoded request bodies to query-string parsing, and raises HTTPInputError when the limit is exceeded.

Detection Methods for CVE-2026-103261

Indicators of Compromise

  • HTTP GET requests whose URIs contain hundreds or thousands of &-separated query parameters.
  • Sudden, sustained rise in Tornado event-loop latency or request handler scheduling delays.
  • Elevated CPU usage within Python processes correlated with inbound HTTP traffic from a small set of source addresses.

Detection Strategies

  • Instrument web application gateways or reverse proxies to count query-string fields per request and alert on counts exceeding a tuned threshold.
  • Compare query-string field distributions against a baseline of normal application traffic and flag outliers.
  • Monitor application logs for HTTPInputError entries after upgrading, which indicate rejected oversize query strings.

Monitoring Recommendations

  • Track per-process event-loop lag and p95/p99 request latency for Tornado services.
  • Capture source IP, URI length, and parameter count in access logs to enable retrospective hunting.
  • Forward web traffic telemetry to a centralized analytics backend such as the Singularity Data Lake for cross-service correlation and historical analysis.

How to Mitigate CVE-2026-103261

Immediate Actions Required

  • Upgrade Tornado to version 6.5.9 or later across all affected deployments.
  • Place rate limiting and query-string size controls at a reverse proxy or web application firewall in front of Tornado services.
  • Audit internet-exposed Tornado endpoints and prioritize patching for multi-tenant workloads sharing an IOLoop.

Patch Information

The fix is delivered in Tornado 6.5.9. Review the upstream commit 03945136, the project advisory GHSA-3hv7-mjh2-fv65, and the VulnCheck Tornado DoS advisory for full remediation guidance.

Workarounds

  • Enforce a maximum query-string length and parameter count at an upstream proxy such as NGINX or HAProxy.
  • Reject or drop requests exceeding reasonable query parameter counts before they reach Tornado.
  • Deploy per-source IP rate limiting to blunt automated flooding of large query strings.
bash
# NGINX reverse proxy example: cap request line and limit request rate
http {
    large_client_header_buffers 4 8k;
    client_header_buffer_size 1k;

    limit_req_zone $binary_remote_addr zone=tornado_rl:10m rate=20r/s;

    server {
        listen 443 ssl;
        location / {
            limit_req zone=tornado_rl burst=40 nodelay;
            if ($query_string ~ "(&[^=&]+=[^&]*){500,}") { return 414; }
            proxy_pass http://tornado_upstream;
        }
    }
}

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

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.