Jun 16, 2026·7 min read·22 visits
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.
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.
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.
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.
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.
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.
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.
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
tornado Tornado | <= 6.5.6 | 6.5.7 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-200, CWE-672 |
| Attack Vector | Network (AV:N) |
| Attack Complexity | High (AC:H) |
| CVSS Score | 5.9 (Medium) |
| Exploit Status | PoC (Proof of Concept) Available |
| KEV Status | Not Listed |
The program exposes sensitive information to an actor that is not authorized to have access to that information.
CVE-2026-67435 is a security vulnerability in the linuxfabrik-lib Python library prior to version 6.0.0. When performing HTTP requests with follow_redirects enabled, custom authentication headers (such as X-Auth-Token or X-Api-Key) are forwarded during cross-origin redirects. A malicious or compromised server can leverage this behavior to capture sensitive monitoring and administrative credentials, leading to potential unauthorized access and Server-Side Request Forgery (SSRF).
CVE-2026-67429 is a critical path traversal vulnerability in Flyto2 Core file-writing modules, including image.download and twelve other modules. By exploiting an insecure validation check that relied on user-controlled parameters, unauthenticated remote attackers can bypass directory confinement and write arbitrary files to the file system, leading to remote code execution.
CVE-2026-67427 is a capability bypass vulnerability in the Flyto2 Core workflow execution kernel. Due to a logical inconsistency in how dynamic parameters are resolved, the system evaluates environment variables via template interpolation prior to executing capability filter validation. This permits unprivileged workflow definitions to completely bypass denylist restrictions on the `env.get` module, exfiltrating critical host configurations, API tokens, and credentials via allowed communication channels.
An insecure credential forwarding vulnerability in Flyto2 Core prior to version 2.26.6 allows attackers to exfiltrate operator API keys. This occurs because the system forwards environment-derived API keys to user-controlled custom endpoints, bypassing SSRF guards designed only for private target validation.
A critical remote code execution (RCE) vulnerability exists in AWS Amplify Studio's code-generation library (@aws-amplify/codegen-ui). An authenticated attacker with permissions to create or modify component schemas can inject malicious JavaScript code into those schemas. When the Amplify CLI or the build environment processes these schemas, the unvalidated expressions are executed within the host Node.js environment, leading to full system compromise.
CVE-2026-67426 is a critical vulnerability in Flyto2 Core prior to version 2.26.7. The standalone flyto-verification service binds to all interfaces (0.0.0.0) on port 8344 and exposes an unauthenticated POST /run endpoint. This endpoint accepts an arbitrary client-controlled callback URL and makes an outbound POST request containing the sensitive internal runner secret in the headers. Attackers can exploit this to retrieve the FLYTO_RUNNER_SECRET and perform Server-Side Request Forgery (SSRF) against internal network targets.