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



GHSA-PW6J-QG29-8W7F

GHSA-pw6j-qg29-8w7f: State Persistence and Sensitive Credential Leakage in Tornado CurlAsyncHTTPClient

Amit Schendel
Amit Schendel
Senior Security Researcher

Jun 16, 2026·7 min read·2 visits

Executive Summary (TL;DR)

Tornado's CurlAsyncHTTPClient reuses pycurl handles without resetting configuration state, leading to client certificate and proxy credential leakage to unintended destinations.

A state persistence vulnerability exists in Tornado's CurlAsyncHTTPClient component where pooled pycurl.Curl handles are reused across asynchronous requests without a complete state reset. Consequently, sensitive per-request configurations, such as client TLS certificates or proxy basic authentication credentials, persist on the shared handle. This behavior leads to subsequent requests leaking these credentials to unauthorized remote servers.

Vulnerability Overview

The Tornado web framework provides multiple backends for handling asynchronous HTTP requests. The CurlAsyncHTTPClient is an advanced backend designed as a wrapper around pycurl, the Python bindings for the C-based libcurl library. This backend is frequently selected in performance-critical environments because of its speed, multi-socket scheduling capabilities, and support for complex network topologies. To achieve high request throughput, CurlAsyncHTTPClient maintains a internal pool of pycurl.Curl easy handles that are checked out of an idle queue, configured for a specific request, and then checked back into the queue upon execution completion.

However, this optimization creates a significant attack surface if security states are not strictly isolated between consecutive execution threads. The client fails to wipe the internal configuration history of these reusable easy handles. When a handle is returned to the free list, any configuration set during the lifetime of the previous request remains active unless explicitly overwritten. Consequently, a request designed with strict security parameters (such as client-side certificates or proxy credentials) will unintentionally pass those configuration values down to any subsequent request scheduled on the same handle.

This structural logic flaw results in two distinct data leakage vectors. The first vector leaks client-side TLS certificates (configured via SSLCERT and SSLKEY) to arbitrary public servers. The second vector leaks proxy authentication credentials to unauthorized intermediate proxy servers. This vulnerability exposes organizations using mutual TLS (mTLS) or credentialed proxy configurations to unauthorized credential harvesting and identity impersonation.

Root Cause Analysis

The fundamental driver of this vulnerability is the state retention model used by the underlying libcurl library. In libcurl, when an option is applied to an easy handle via curl_easy_setopt (wrapped by Python's curl.setopt()), the setting persists on that handle indefinitely. The setting remains active until it is overridden by another setopt call, cleared using curl_easy_unsetopt, or wiped using curl_easy_reset (wrapped by curl.reset()). Tornado's CurlAsyncHTTPClient does not reset handles between operations, opting instead to configure options dynamically on a per-request basis in the _curl_setup_request method.

This design operates on the assumption that _curl_setup_request will comprehensively configure or clear every parameter. This assumption is incorrect. In tornado/curl_httpclient.py (v6.5.6 and earlier), the setup routine implements multiple conditional checks that apply options only when a specific configuration is active, lacking corresponding else branches to clear those parameters when they are absent in subsequent requests.

For example, during mTLS requests, SSLCERT and SSLKEY parameters are bound to the pycurl.Curl instance. If a subsequent request that does not specify client certificates is allocated the same handle, the configuration block is skipped, allowing the previously bound paths to persist. During proxy operations, if the client establishes a request using an authenticated proxy (configuring PROXYUSERPWD) and then issues a subsequent request through an unauthenticated proxy, the code updates the proxy host and port but bypasses the credential update block. Because the unsetting logic is only reachable when proxying is disabled entirely, the previous credentials remain bound and are transmitted to the new proxy server.

Code-Level Flaw Analysis

The vulnerable logic exists in tornado/curl_httpclient.py inside the _curl_setup_request method. The following block displays how client certificates are configured conditionally without cleanup branches:

# Vulnerable code in tornado/curl_httpclient.py (v6.5.6)
if request.client_cert is not None:
    curl.setopt(pycurl.SSLCERT, request.client_cert)
 
if request.client_key is not None:
    curl.setopt(pycurl.SSLKEY, request.client_key)

Because there are no corresponding else conditions, a handle that has request.client_cert configured will continue to hold that reference on all subsequent requests where request.client_cert is None. A similar issue affects proxy credential management:

# Vulnerable proxy credentials logic
if request.proxy_host and request.proxy_port:
    curl.setopt(pycurl.PROXY, request.proxy_host)
    curl.setopt(pycurl.PROXYPORT, request.proxy_port)
    if request.proxy_username:
        assert request.proxy_password is not None
        credentials = httputil.encode_username_password(
            request.proxy_username, request.proxy_password
        )
        curl.setopt(pycurl.PROXYUSERPWD, credentials)
    # Missing 'else' branch to clear PROXYUSERPWD when proxy_username is None
else:
    try:
        curl.unsetopt(pycurl.PROXY)
    except TypeError:
        curl.setopt(pycurl.PROXY, "")
    curl.unsetopt(pycurl.PROXYUSERPWD)

In this block, if request.proxy_host remains true but request.proxy_username is omitted, the code does not reach the outer else block containing curl.unsetopt(pycurl.PROXYUSERPWD). Consequently, the credentials from the prior proxy configuration are reused.

The security patches in Tornado version 6.5.7 resolve these issues by implementing systematic unsetting logic. When optional parameters are omitted from a request, the framework explicitly executes curl.unsetopt() to clear the active state of the handle before execution.

Exploitation and Attack Methodology

Exploiting this vulnerability does not require complex payload construction. It relies instead on scheduling behavior and pool reuse. An attacker does not need direct access to the application's memory or network flow to trigger the leak. Instead, the application itself initiates the leakage when executing sequential requests on behalf of users.

In a multi-tenant or multi-destination environment, an application using CurlAsyncHTTPClient might first process an internal request (Request A) that requires mutual TLS authentication to access a restricted backend service. Once completed, the handle is returned to the pool. When a user subsequently triggers a request (Request B) to an external, untrusted, or attacker-controlled server, the backend scheduler may assign the same handle. Because the handle retains the SSLCERT and SSLKEY parameters, the outgoing connection to the attacker-controlled server will execute an mTLS handshake, automatically presenting the client certificate to the external target.

In proxy scenarios, the process follows a similar flow. An application routes administrative or premium traffic through an authenticated corporate proxy using a high-privilege credential. When the application subsequently routes a standard user request through an unauthenticated public proxy, the active handle retains the Proxy-Authorization header value. The unauthorized proxy captures the header during connection establishment, obtaining the base64-encoded credentials of the primary corporate proxy.

Impact Assessment

The impact of credential leakage is classified as Medium (CVSS 5.9). Although the base CVSS score is moderate due to high attack complexity, the actual consequences in enterprise deployments can be severe.

In modern service architectures, client TLS certificates are frequently used as a primary authentication factor to access protected APIs, internal administrative portals, or sensitive databases. If these certificates are leaked to external servers, an attacker can capture the certificate chain. Depending on the configuration of the certificate authority and the target services, this exposure can allow the attacker to authenticate as the client, leading to unauthorized data access.

Furthermore, client certificates frequently contain metadata within their Subject Common Name (CN) or Alternative Names, such as internal server names, IP addresses, or domain structures. Access to this metadata allows an attacker to map internal network layouts. Similarly, leaked proxy credentials can allow unauthorized users to tunnel traffic through enterprise proxy servers, bypassing access controls and incurring significant routing fees.

Remediation and Defensive Strategies

The primary remediation strategy is upgrading the Tornado installation to version 6.5.7 or later. The update implements code changes that explicitly clear unused parameters during request setup, preventing state persistence across requests.

If upgrading is not immediately possible, you can implement several workarounds to mitigate exposure. The first workaround is switching the application's HTTP client backend from CurlAsyncHTTPClient to Tornado's native python-based client, SimpleAsyncHTTPClient. Because the native client does not use libcurl or pool persistent native handles, it is entirely unaffected by this state leakage flaw.

# Configure Tornado to use the safe native client backend
from tornado.httpclient import AsyncHTTPClient
AsyncHTTPClient.configure("tornado.simple_httpclient.SimpleAsyncHTTPClient")

If your application requires the CurlAsyncHTTPClient for advanced networking features, you can mitigate the risk by isolating client instances based on security context. Instead of using a single global client, instantiate separate client pools for different classes of requests. For example, use one dedicated client instance exclusively for mTLS connections, another for authenticated proxy connections, and a third for standard outbound requests. This isolation ensures that a recycled handle is never reused across different security boundaries.

Technical Appendix

CVSS Score
5.9/ 10
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N

Affected Systems

Tornado Web Framework (CurlAsyncHTTPClient)

Affected Versions Detail

Product
Affected Versions
Fixed Version
tornado
Tornado
<= 6.5.66.5.7
AttributeDetail
CWE IDCWE-200, CWE-672
Attack VectorNetwork (AV:N)
Attack ComplexityHigh (AC:H)
CVSS Score5.9 (Medium)
Exploit StatusPoC (Proof of Concept) Available
KEV StatusNot Listed

MITRE ATT&CK Mapping

T1552Unsecured Credentials
Credential Access
T1552.004Private Keys
Credential Access
T1083File and Directory Discovery
Discovery
CWE-200
Exposure of Sensitive Information to an Unauthorized Actor

The program exposes sensitive information to an actor that is not authorized to have access to that information.

Vulnerability Timeline

Vulnerability details coordinated and published under GHSA-PW6J-QG29-8W7F.
2026-06-15
Official repository security advisory and patch released in Tornado version 6.5.7.
2026-06-15
Public release of Proof of Concept scripts demonstrating credential leakage.
2026-06-15

References & Sources

  • [1]GitHub Security Advisory GHSA-pw6j-qg29-8w7f
  • [2]Tornado Security Advisory
  • [3]Affected Source File (v6.5.6)

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 3 hours ago•CVE-2026-48748
7.5

CVE-2026-48748: Netty HTTP/3 QPACK Blocked Streams Memory Exhaustion

CVE-2026-48748 is a denial-of-service vulnerability in Netty's HTTP/3 codec (netty-codec-http3) occurring when QPACK dynamic tables are enabled but the blocked streams limit is not explicitly configured. A bug in limit checking and a memory leak in stream tracking allow unauthenticated remote attackers to exhaust the JVM heap memory and crash the server.

Amit Schendel
Amit Schendel
2 views•6 min read
•about 3 hours ago•CVE-2026-50009
4.8

CVE-2026-50009: Stateless Reset Token Exposure in Netty QUIC

CVE-2026-50009 is a cryptographic design vulnerability in the Netty network application framework. Prior to version 4.2.15.Final, the framework's QUIC protocol implementation fails to cryptographically segregate the generated Connection IDs and the associated Stateless Reset Tokens. An on-path network attacker who sniffs traffic during a Connection ID rotation can extract secret token material from cleartext headers, enabling them to inject spoofed reset packets and terminate active connections.

Alon Barad
Alon Barad
2 views•6 min read
•about 4 hours ago•CVE-2026-50010
7.5

CVE-2026-50010: Hostname Verification Bypass in Netty TLS Client

A critical hostname verification bypass vulnerability exists in the Netty network application framework when configured as a TLS client. When a developer registers a custom plain X509TrustManager, Netty wraps it inside an X509TrustManagerWrapper to adapt it to the X509ExtendedTrustManager API. However, this wrapper discards the SSLEngine context, bypassing critical hostname checks. Because the wrapper is identified as an X509ExtendedTrustManager, standard cryptographic engines and Netty's OpenSSL wrappers do not re-wrap it, failing to execute any hostname validation. Consequently, clients silently accept certificates for any host, enabling unauthenticated Man-in-the-Middle (MitM) attacks.

Amit Schendel
Amit Schendel
2 views•8 min read
•about 4 hours ago•CVE-2026-50011
7.5

CVE-2026-50011: Unbounded Resource Pre-Allocation in Netty Redis Codec

An uncontrolled resource pre-allocation flaw in the Netty Redis codec module allows remote unauthenticated attackers to cause a denial of service (OutOfMemoryError) by sending a crafted Redis Serialization Protocol (RESP) array header.

Amit Schendel
Amit Schendel
3 views•7 min read
•about 5 hours ago•CVE-2026-50020
5.3

CVE-2026-50020: HTTP Request Smuggling in Netty HttpObjectDecoder via Arbitrary Leading Control Bytes

CVE-2026-50020 is a medium-severity HTTP Request Smuggling/Response Smuggling vulnerability (CWE-444) within the Netty asynchronous network application framework. The flaw resides in Netty's HTTP codec implementation, specifically the HttpObjectDecoder class, which silently consumes arbitrary ISO control bytes preceding the first request line.

Alon Barad
Alon Barad
3 views•7 min read
•about 5 hours ago•CVE-2026-50560
6.9

CVE-2026-50560: Denial of Service in Netty HTTP/2 Codec via Max Header List Size Exception

CVE-2026-50560 describes a vulnerability in Netty's HTTP/2 codec implementation. When acting as an intermediary (such as a reverse proxy, API gateway, or edge server), Netty can be forced into an application-level Denial-of-Service condition. The attack is triggered by negotiating a restrictive SETTINGS_MAX_HEADER_LIST_SIZE from the client, causing Netty to process incoming requests fully, but subsequently crash or abort during outbound response serialization. This results in an asymmetrical consumption of resources on backend systems and thread starvation within the Netty event loop.

Alon Barad
Alon Barad
3 views•6 min read