CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-116578

CVE-2026-116578: Open Redirect in Tornado RedirectHandler via URL Normalization and Template Substitution

Alon Barad
Alon Barad
Software Engineer

Oct 29, 2026·11 min read·3 visits

Executive Summary (TL;DR)

Tornado < 6.6.0 allows open redirects in RedirectHandler when URL templates place regex substitutions at the start of a path or directly after a hostname. Attackers can supply protocol-relative URLs, backslashes, or authority-altering payloads to redirect users to untrusted external domains.

Tornado versions prior to 6.6.0 contain an open redirect vulnerability (CWE-601) in tornado.web.RedirectHandler. When route templates incorporate regular expression capture groups at the start of a path, directly after a fixed host without a trailing slash, or at the beginning of a destination template, unvalidated URL-decoded parameters allow attackers to manipulate the redirect scheme or network location. Because modern browsers normalize URLs according to the WHATWG specification—treating backslashes as forward slashes and stripping control characters—attackers can bypass naive prefix checks and redirect victims to arbitrary external domains.

Vulnerability Overview

Tornado is an asynchronous networking library and web framework for Python designed to handle high-concurrency HTTP workloads. Within its core routing module (tornado.web), the framework provides RedirectHandler, a built-in request handler used to issue HTTP 301 or 302 redirects from matched incoming request paths to a target destination URL. Developers configure RedirectHandler by defining a route regular expression with capture groups and passing a target url format string in the route initialization parameters.

In versions of Tornado prior to 6.6.0, RedirectHandler substitutes URL-decoded regular expression capture groups directly into the target template using standard Python string formatting (self._url.format(*args, **kwargs)). No structural validation is performed to ensure that the substituted parameters do not alter the scheme or network location (netloc) of the resulting URL compared to the static template definition. This flaw is classified under CWE-601: URL Redirection to Untrusted Site ('Open Redirect').

When an application places a format substitution at the beginning of a path (e.g., /{0}), directly after a fixed hostname without a separating slash (e.g., https://example.com{0}), or at the very start of the template (e.g., {0}), an attacker can craft a request path that overrides the intended redirect destination. Static templates without substitutions, as well as templates where substitutions occur strictly after a fixed host and a trailing forward slash (e.g., https://example.com/{0}), are not susceptible to network location overrides.

Successful exploitation requires the victim to click or navigate to a crafted URL hosted on the vulnerable Tornado application's domain. When processed by the server, the RedirectHandler emits a Location response header pointing to an attacker-controlled domain. This allows adversaries to abuse the reputation and TLS context of the trusted application domain to conduct spearphishing, credential harvesting, or OAuth token redirection attacks.

Root Cause Analysis

The root cause of CVE-2026-116578 lies in two architectural factors: the lack of structural URL parsing after string interpolation in RedirectHandler.get(), and parser differentials between standard Python string manipulation and the WHATWG URL standard implemented by modern web browsers.

When Tornado dispatches an incoming HTTP request to a RequestHandler, it extracts regular expression capture groups from the request URI path and applies URL percent-decoding (urllib.parse.unquote) before passing them as positional (*args) or keyword (**kwargs) arguments to the handler's HTTP method. In RedirectHandler.get(), these decoded strings are interpolated directly into self._url. Because percent-decoding happens prior to interpolation, encoded characters such as %5C (backslash) or %09 (horizontal tab) are injected into the destination string as raw literal characters.

Even when naive prefix validation is attempted—such as checking to_url.startswith("//") to block protocol-relative URLs—attackers can bypass the check by exploiting browser URL normalization rules. According to the WHATWG URL specification, web browsers perform several preprocessing transformations before resolving a redirect Location header:

  1. Backslash Normalization: In special schemes (including http: and https:) and relative URLs resolved against them, browsers treat reverse solidus (\) characters as equivalent to forward solidus (/) characters. Thus, /\evil.com and \\evil.com are normalized to //evil.com, which resolves as a scheme-relative redirect to the host evil.com.
  2. Control Character and Whitespace Stripping: Browsers strip all leading and trailing C0 control characters and spaces (ASCII bytes 0x00 through 0x20). Furthermore, browsers strip all embedded ASCII tab (\t, 0x09), newline (\n, 0x0A), and carriage return (\r, 0x0D) characters from anywhere within the URL string prior to parsing. A payload such as /\t/evil.com evades a literal startswith("//") check on the server, but collapses into //evil.com inside the client browser.

Beyond protocol-relative path prefixes, string interpolation without component boundary verification allows authority manipulation when a substitution immediately follows a fixed hostname (e.g., https://example.com{0}). If the substituted value begins with .evil.com, the resulting URL becomes https://example.com.evil.com, shifting the target host to an external domain. Similarly, if the substituted value begins with @evil.com, the string example.com is reinterpreted by the URL parser as the userinfo component, directing the browser to authenticate and connect to the host evil.com.

Code Analysis

The remediation of CVE-2026-116578 in Pull Request #3786 evolved across three distinct commits, illustrating the pitfalls of blacklist-based string prefix checking versus structural URL invariant verification.

In the initial fix attempt (commit 940ea725bdd9bc68231d68a105eef17ea6e11233), the maintainer added a literal string prefix check to RedirectHandler.get() to verify that to_url did not begin with // unless the underlying self._url template also began with //:

# Initial incomplete fix in 940ea725bdd9bc68231d68a105eef17ea6e11233
if to_url.startswith("//") and not self._url.startswith("//"):
    raise ValueError(
        "Redirect URL cannot start with '//' if template does not."
    )

As subsequent review identified, this check was insufficient because it failed to account for WHATWG browser normalization of backslashes and whitespace, and it raised an unhandled ValueError (resulting in an HTTP 500 Internal Server Error rather than an HTTP 400 Bad Request). Commit ccb6a2e5482f17997efd4a74b8200d5b4babc035 introduced a helper function, _is_protocol_relative(), which stripped ASCII bytes 0x00-0x20 and removed \t, \n, and \r before checking if the first two characters were any combination of / or \. This helper was also applied to StaticFileHandler.validate_absolute_path.

However, _is_protocol_relative() still did not address templates where substitutions occurred at index 0 (allowing full schemes like https://evil.com) or directly after a fixed host (allowing .evil.com or @evil.com netloc manipulation). In the final comprehensive fix (commit cc75ab0eb3af459c714895a49f682ad354f50117), Tornado replaced prefix checking in RedirectHandler with a structural invariant check that compares the parsed scheme and netloc of the formatted URL against a baseline generated by formatting the template with benign / placeholders:

# Final patch in tornado/web.py (commit cc75ab0eb3af459c714895a49f682ad354f50117)
def get(self, *args: Any, **kwargs: Any) -> None:
    to_url = self._url.format(*args, **kwargs)
    # The scheme and netloc of the redirect must come from the template, not
    # from the request. Find out what they are by formatting the template
    # with a placeholder that cannot extend a scheme or netloc.
    expected_url = self._url.format(
        *("/" for _ in args), **{k: "/" for k in kwargs}
    )
    try:
        # Verify that user-supplied arguments did not alter scheme or netloc
        unchanged = _scheme_and_netloc(to_url) == _scheme_and_netloc(expected_url)
    except ValueError:
        unchanged = False
    if not unchanged:
        raise HTTPError(400, "Redirect URL cannot change the scheme or netloc")
    if self.request.query_arguments:
        to_url = httputil.url_concat(
            to_url,
            list(httputil.qs_to_qsl(self.request.query_arguments)),
        )
    self.redirect(to_url, permanent=self._permanent)

To ensure that Python's urllib.parse.urlsplit evaluates the URL identically to a client browser, the patch introduces _normalize_url_like_browser() and _scheme_and_netloc():

def _normalize_url_like_browser(url: str) -> str:
    """Approximates how a browser cleans up a URL before parsing it."""
    # Strip leading and trailing C0 controls and space (0x00 - 0x20)
    url = url.strip("".join(chr(i) for i in range(0x21)))
    # Remove embedded tab, LF, and CR characters
    url = url.replace("\t", "").replace("\n", "").replace("\r", "")
    # Normalize backslashes to forward slashes
    return url.replace("\\", "/")
 
def _is_protocol_relative(url: str) -> bool:
    return _normalize_url_like_browser(url).startswith("//")
 
def _scheme_and_netloc(url: str) -> tuple[str, str]:
    """Returns the scheme and netloc a browser would see in ``url``."""
    parts = urllib.parse.urlsplit(_normalize_url_like_browser(url))
    return parts.scheme.lower(), parts.netloc.lower()

> [!NOTE] > Fix Completeness Assessment: By normalizing C0 control characters, whitespace, and backslashes prior to invoking urllib.parse.urlsplit(), and asserting that (_scheme, _netloc) remains invariant between the dummy placeholder URL (expected_url) and the runtime formatted URL (to_url), the fix effectively neutralizes protocol-relative injection, scheme overrides, userinfo injection, and host suffix extension. Note that as an intentional design consequence, Tornado 6.6.0 no longer permits intentional regex substitutions inside the scheme or netloc portions of a RedirectHandler template.

Exploitation

Exploiting CVE-2026-116578 requires no authentication (PR:N), though it depends on the target application configuring RedirectHandler with vulnerable parameter placements (AT:P) and requires active user interaction (UI:A) where a victim clicks a crafted link. Three primary exploitation patterns exist depending on how the application defines its routing table.

Pattern 1: Reordered or Leading Path Substitutions

When a route captures multiple segments and places a non-initial capture group at the start of a relative redirect path, an attacker can inject a leading slash, backslash, or tab sequence to construct a protocol-relative URL.

# Vulnerable route configuration
app = tornado.web.Application([
    (r"/(.*?)/(.*?)/(.*)", tornado.web.RedirectHandler, {"url": "/{1}/{0}/{2}"}),
])

Because the router consumes the initial / before the first group, {1} is placed immediately after the leading / of the template. An attacker can send the following HTTP requests:

# Vector 1A: Raw double-slash injection
GET /a//evil.com/b HTTP/1.1
Host: vulnerable-app.example
 
# Response:
HTTP/1.1 301 Moved Permanently
Location: //evil.com/a/b
# Vector 1B: URL-encoded backslash smuggling
GET /a/%5Cevil.com/b HTTP/1.1
Host: vulnerable-app.example
 
# Response:
HTTP/1.1 301 Moved Permanently
Location: /\evil.com/a/b
# Vector 1C: Horizontal tab smuggling
GET /a/%09/evil.com/b HTTP/1.1
Host: vulnerable-app.example
 
# Response:
HTTP/1.1 301 Moved Permanently
Location: /	/evil.com/a/b

In all three cases above, a compliant browser normalizes the Location header value to //evil.com/a/b and navigates the user to evil.com over the current scheme (http or https).

Pattern 2: Host Extension and Userinfo Authority Shift

When a route captures a suffix and appends it directly to a fixed domain name without a separating / in the template, the attacker controls the trailing characters of the authority section:

# Vulnerable route configuration
app = tornado.web.Application([
    (r"/moved(.*)", tornado.web.RedirectHandler, {"url": "https://example.com{0}"}),
])

An attacker can exploit this configuration via subdomain suffixing or @ authority redirection:

# Vector 2A: Host extension
GET /moved.evil.com/login HTTP/1.1
Host: vulnerable-app.example
 
# Response:
HTTP/1.1 301 Moved Permanently
Location: https://example.com.evil.com/login
# Vector 2B: Userinfo injection
GET /moved@evil.com/login HTTP/1.1
Host: vulnerable-app.example
 
# Response:
HTTP/1.1 301 Moved Permanently
Location: https://example.com@evil.com/login

Pattern 3: Unanchored Template Substitutions

If a template starts directly with {0} (e.g., (r"/goto/(.*)", RedirectHandler, {"url": "{0}"})), the attacker can supply an arbitrary scheme such as GET /goto/https://evil.com/ or encoded double backslashes GET /goto/%5C%5Cevil.com/ to redirect the client to an arbitrary external origin.

Impact Assessment

The severity of CVE-2026-116578 is rated Low with a CVSS v4.0 base score of 2.1 (CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:A/VC:N/VI:N/VA:N/SC:N/SI:L/SA:N). The vulnerability does not compromise the confidentiality, integrity, or availability of the vulnerable Tornado server itself (VC:N/VI:N/VA:N). Instead, the impact manifests on the subsequent system (SI:L)—specifically, the client browser environment and the user's trust context.

Open redirects on legitimate domains are primarily leveraged as an initial access enabler (MITRE ATT&CK technique T1566.002: Phishing: Spearphishing Link). Because the initial link domain belongs to a trusted organization and passes standard email gateway domain-reputation filters, users are more likely to click the link. Once clicked, the HTTP 301/302 redirect seamlessly forwards the user's browser to an attacker-controlled phishing page designed to harvest credentials.

In more complex architectures, open redirects within an application's origin can be chained with other web vulnerabilities. For example, if the application acts as an OAuth 2.0 / OpenID Connect client or identity provider and validates redirect_uri parameters using permissive prefix matching on its own domain, an attacker can chain an open redirect on the trusted domain to exfiltrate authorization codes or access tokens passed in URL query parameters or fragments.

According to Exploit Prediction Scoring System (EPSS) telemetry as of October 28, 2026, CVE-2026-116578 has an EPSS score of 0.00459 (0.46% probability of exploitation within 30 days, 39.66th percentile), and it is not listed in the CISA Known Exploited Vulnerabilities (KEV) catalog.

Remediation

The primary remediation is to upgrade the tornado package to version 6.6.0 or later, which enforces strict scheme and network location invariance in RedirectHandler and returns an HTTP 400 Bad Request when an incoming request attempts to modify either component.

pip install --upgrade "tornado>=6.6.0"

When upgrading to Tornado 6.6.0, developers should verify that none of their existing RedirectHandler route templates intentionally place format placeholders ({0}, {name}) inside the URL scheme or hostname/port (netloc) sections. Under the new validation logic in 6.6.0, any template that attempts to dynamically populate the hostname via regex captures will fail the baseline comparison against expected_url and raise HTTPError(400).

For environments where an immediate upgrade to Tornado 6.6.0 is not possible, development teams should audit all route definitions using tornado.web.RedirectHandler and apply the following configuration changes:

  1. Enforce Fixed Host Slash Boundaries: Never define a RedirectHandler template where a placeholder immediately follows the hostname (e.g., {"url": "https://example.com{0}"}). Always include an explicit trailing forward slash in the static template and match the leading slash in the route regex:
# Vulnerable configuration:
(r"/moved(.*)", tornado.web.RedirectHandler, {"url": "https://example.com{0}"})
 
# Remediated configuration (safe even on < 6.6.0 if path cannot shift netloc):
(r"/moved/(.*)", tornado.web.RedirectHandler, {"url": "https://example.com/{0}"})
  1. Sub-class RedirectHandler with Backported Normalization: If relative path templates such as "/{0}/dest" must be used on older Tornado versions, replace tornado.web.RedirectHandler with a custom subclass that implements _normalize_url_like_browser and validates urllib.parse.urlsplit components prior to calling self.redirect().

Official Patches

TornadoGitHub Security Advisory GHSA-pw9p-v7r6-gx4p
TornadoPull Request #3786 containing the fix commits
TornadoTornado v6.6.0 Release Notes

Fix Analysis (3)

Technical Appendix

CVSS Score
2.1/ 10
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:A/VC:N/VI:N/VA:N/SC:N/SI:L/SA:N
EPSS Probability
0.46%
Top 60% most exploited

Affected Systems

tornadoweb/tornado (PyPI package: tornado)

Affected Versions Detail

Product
Affected Versions
Fixed Version
tornado
tornadoweb
< 6.6.06.6.0
AttributeDetail
Vulnerability TypeCWE-601: URL Redirection to Untrusted Site ('Open Redirect')
Affected Componenttornado.web.RedirectHandler (versions < 6.6.0)
Attack VectorNetwork (Crafted URI path with active user interaction)
CVSS v4.0 Score2.1 Low (CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:A/VC:N/VI:N/VA:N/SC:N/SI:L/SA:N)
EPSS Score0.46% (39.66th percentile)
Exploit StatusProof-of-Concept payloads publicly documented in advisory
CISA KEV StatusNot Listed
Fixed VersionTornado 6.6.0

MITRE ATT&CK Mapping

T1566.002Phishing: Spearphishing Link
Initial Access
CWE-601
URL Redirection to Untrusted Site ('Open Redirect')

A web application accepts a user-controlled input that specifies a link to an external site, and uses that link in a Redirect. This simplifies phishing attacks.

Vulnerability Timeline

Initial commit 940ea725bdd9bc68231d68a105eef17ea6e11233 authored to block literal '//' prefixes in RedirectHandler.
2026-10-06
Commits ccb6a2e5482f17997efd4a74b8200d5b4babc035 and cc75ab0eb3af459c714895a49f682ad354f50117 authored in PR #3786 to address WHATWG normalization bypasses and enforce scheme/netloc invariance.
2026-10-10
Tornado 6.6.0 released; CVE-2026-116578 and GHSA-pw9p-v7r6-gx4p published.
2026-10-26
EPSS score calculated and vulnerability advisories synchronized across public databases.
2026-10-28

References & Sources

  • [1]GitHub Security Advisory: GHSA-pw9p-v7r6-gx4p
  • [2]Tornado Pull Request #3786
  • [3]Fix Commit 1: 940ea725bdd9bc68231d68a105eef17ea6e11233
  • [4]Fix Commit 2: ccb6a2e5482f17997efd4a74b8200d5b4babc035
  • [5]Fix Commit 3: cc75ab0eb3af459c714895a49f682ad354f50117
  • [6]Tornado v6.6.0 Release
  • [7]CVE.org Record: CVE-2026-116578
  • [8]NVD Detail: CVE-2026-116578
  • [9]CVEListV5 JSON Record: CVE-2026-116578

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

More Reports

•about 1 hour ago•CVE-2026-116577
5.3

CVE-2026-116577: Algorithmic Complexity Denial of Service in Tornado HTTP Header Parser

Tornado versions prior to 6.6.0 contain an inefficient algorithmic complexity vulnerability in the HTTP header parser. When processing requests with obsolete line folding (obs-fold), the HTTPHeaders.parse_line method repeatedly concatenates immutable Python strings within a list structure. An unauthenticated remote attacker can send an HTTP request containing thousands of short header continuation lines to trigger quadratic O(N^2) CPU consumption on the main IOLoop thread, starving concurrent connections and resulting in a denial of service.

Alon Barad
Alon Barad
2 views•10 min read
•about 3 hours ago•CVE-2026-115927
5.3

CVE-2026-115927: Event Loop Blocking and Memory Amplification in Tornado Chunked Transfer Encoding Parser

Tornado versions prior to 6.6.0 contain an uncontrolled resource consumption vulnerability (CWE-400) in the HTTP/1.1 chunked transfer encoding parser. When processing incoming chunked payloads that have already buffered in the socket read buffer, Tornado parses chunks in a tight synchronous loop without yielding to the event loop. Furthermore, non-streaming request handlers and SimpleAsyncHTTPClient store each chunk as a discrete Python bytes object inside a list, inducing an approximately 20-fold memory overhead for 1-byte chunks.

Amit Schendel
Amit Schendel
5 views•11 min read
•about 4 hours ago•CVE-2026-117601
9.3

CVE-2026-117601: Improper Cryptographic Signature Verification in Electrum Bitcoin Wallet

A missing cryptographic signature verification vulnerability exists in Electrum Bitcoin Wallet versions prior to 4.8.3. In Electrum's Simplified Payment Verification (SPV) architecture, the client failed to validate input signatures on unconfirmed mempool transactions received from connected Electrum servers. A malicious Electrum server can exploit this flaw by intercepting a legitimate user broadcast, suppressing it, and returning a spoofed unconfirmed transaction with an invalid signature and zero fee that redirects the user's UTXOs to an attacker-controlled address. When the user initiates a Replace-By-Fee (RBF) action to accelerate the seemingly stuck transaction, Electrum signs the spoofed outputs with the user's private keys, resulting in the direct theft of funds.

Amit Schendel
Amit Schendel
9 views•10 min read
•about 5 hours ago•CVE-2026-118254
8.6

CVE-2026-118254: DOM-based Cross-Site Scripting in Highcharts Grid Pagination Module

CVE-2026-118254 (GHSA-r5wg-q8fc-p6w2) is a high-severity DOM-based Cross-Site Scripting (DOM XSS) vulnerability in the pagination module of Highcharts Grid (@highcharts/grid-lite and @highcharts/grid-pro). The flaw stems from the direct assignment of localization configuration strings (lang.pageInfo and lang.pageSizeLabel) and pagination UI labels to DOM elements via the innerHTML property instead of text-safe DOM APIs such as textContent or innerText. When an application passes attacker-controlled data into the Grid localization options with pagination enabled, arbitrary HTML and JavaScript execute automatically within the hosting document's origin context upon component rendering.

Amit Schendel
Amit Schendel
7 views•10 min read
•about 6 hours ago•CVE-2026-118255
8.4

CVE-2026-118255: Cross-Site Scripting via Unfiltered Credits Href in Highcharts Grid

CVE-2026-118255 is a Cross-Site Scripting (XSS) vulnerability in Highcharts Grid and Highcharts Grid Pro. The flaw occurs because the credits component assigns the configuration-supplied credits.href property directly to an HTML anchor element without verifying the URI scheme against the internal Abstract Syntax Tree (AST) allowlist. An attacker who can influence the grid configuration can supply a javascript: URI, resulting in arbitrary JavaScript execution in the victim's browser session when the credits link is clicked.

Amit Schendel
Amit Schendel
8 views•9 min read
•about 11 hours ago•GHSA-F897-62HF-6GXQ
7.5

GHSA-f897-62hf-6gxq: Unbounded Request Body and JSON-RPC Batch DoS in Model Context Protocol TypeScript SDK

The Model Context Protocol (MCP) TypeScript SDK contains an uncontrolled resource consumption vulnerability in its HTTP server transport implementations and middleware adapters. Affected versions fail to enforce upper bounds on incoming HTTP POST request body sizes and JSON-RPC 2.0 batch array lengths. In addition, middleware wrappers for Express and Hono parse request bodies into memory prior to validating Host and Origin headers. Unauthenticated remote attackers can exploit these flaws to trigger Out-Of-Memory (OOM) process crashes or monopolize the single-threaded Node.js event loop.

Amit Schendel
Amit Schendel
6 views•11 min read