Aug 4, 2026·8 min read·3 visits
Prior to version 3.14.2, aiohttp transitions its protocol state to 'upgraded' before reading the body of a WebSocket upgrade request. If the handler rejects the upgrade, any unconsumed body bytes are parsed as a new pipelined HTTP request, enabling request smuggling.
An asynchronous HTTP client/server framework for asyncio and Python, aiohttp prior to version 3.14.2 is vulnerable to HTTP Request Smuggling. The server-side HTTP parser immediately transitions the protocol state to 'upgraded' upon receiving a WebSocket upgrade request before consuming the accompanying request body. If the backend handler rejects the upgrade request while keeping the TCP connection alive, the unconsumed request body remains in the socket buffer and is parsed as a subsequent pipelined HTTP request. This allows an attacker to smuggle requests, bypass frontend reverse proxy controls, and perform unauthorized actions.
The vulnerability CVE-2026-69243 resides in the server-side HTTP parser of the aiohttp framework, an asynchronous HTTP client/server implementation for Python. The affected component is responsible for processing incoming HTTP requests, determining message boundaries, and managing state transitions such as protocol upgrades. The attack surface is exposed to any network-accessible interface running an aiohttp server that handles HTTP/1.1 pipelined connections or uses reverse proxies.\n\nUnder normal conditions, a client initiates a WebSocket connection by sending an HTTP GET request containing 'Connection: Upgrade' and 'Upgrade: websocket' headers. The parser must process the headers, determine if a body exists, consume the body if present, and then hand over the socket to the WebSocket handler. This transition must adhere strictly to RFC 9110 Section 7.8, which mandates that a server must not switch protocol states until the entire upgrade request has been fully read and processed.\n\nThe vulnerability occurs because the aiohttp server-side parser transitions the socket state to 'upgraded' prior to consuming the request body. If the application-level handler rejects the WebSocket upgrade (for instance, due to failed authentication or missing headers) and sends an HTTP error response, the connection remains open. Because the upgrade state was set prematurely, the parser ignores the declared request body, leaving those bytes in the TCP socket's read buffer to be parsed as a subsequent request. This inconsistent interpretation of message boundaries leads to HTTP Request Smuggling (CWE-444).
The root cause of this vulnerability lies in the early assignment of the state variable self._upgraded = True inside the HTTP parsing logic of both the Cython-based parser (aiohttp/_http_parser.pyx) and the fallback pure-Python parser (aiohttp/http_parser.py). This early assignment occurs during the header-parsing phase, immediately when the Upgrade header is identified, rather than waiting for the payload parser to finish consuming the request body.\n\nWhen self._upgraded is set to True during header processing, the standard HTTP body framing checks are bypassed. The parser logic assumes that the connection has switched to a non-HTTP protocol (e.g., WebSocket binary frames) and stops looking for standard HTTP body framing such as Content-Length or chunked transfer encoding. However, the client has sent a body payload as part of the upgrade request. Since the server does not consume this body, the unconsumed bytes remain buffered in the socket.\n\nIf the application handler rejects the upgrade request, it returns a standard HTTP error response (e.g., 400 Bad Request) over the active TCP connection. The connection remains open if keep-alive is active. When the client sends another request, or when the server processes the next message in the pipeline, the server's parser is reset to the HTTP state. It reads the remaining socket buffer, which contains the unconsumed body of the previous upgrade request, and mistakenly interprets this body as a new, independent HTTP request sent by the client. This mismatch between proxy-level framing and backend-level framing allows request smuggling.
The security advisory and patch commit 6ae358f0983c3f4d6f67692b2f8e65dc8e091c98 demonstrate the differences between the vulnerable and patched code paths. In the vulnerable version, the Cython parser in _http_parser.pyx set _upgraded = True inside the header processing block:\n\npython\n# Vulnerable code path in _http_parser.pyx\nif (upgrade and h_upg.isascii() and h_upg.lower() in ALLOWED_UPGRADES) or self._cparser.method == cparser.HTTP_CONNECT:\n self._upgraded = True\n\n\nThe patch replaces this premature assignment with a deferred state variable _pending_upgrade. The modified code prevents the parser from switching to the upgraded state until the body parser has explicitly finished reading the HTTP request body:\n\npython\n# Patched code path in _http_parser.pyx\nif (upgrade and h_upg.isascii() and h_upg.lower() in ALLOWED_UPGRADES) or self._cparser.method == cparser.HTTP_CONNECT:\n # Defer the protocol switch until the complete request has been received\n self._pending_upgrade = True\n\n\nSimilarly, in the pure-Python fallback implementation (http_parser.py), the assignment self._upgraded = msg.upgrade and _is_supported_upgrade(msg.headers) was removed from the header parsing stage. Instead, self._pending_upgrade is set if a body payload parser is created. Once the payload parser completes its processing and reads the body to completion, the parser transitions self._upgraded = True and clears self._pending_upgrade:\n\npython\n# Execution block when payload parser finishes reading\nif self._pending_upgrade:\n # Body fully read: the deferred upgrade takes effect and the rest of the connection is the upgraded protocol\n self._upgraded = True\n self._pending_upgrade = False\n\n\nThis deferred state transition ensures complete compliance with RFC 9110 by guaranteeing that all body bytes are drained from the stream before any protocol-switching mechanics are activated. If the application handler subsequently rejects the upgrade, no unconsumed body bytes remain in the socket buffer to corrupt the next HTTP parser cycle.
Exploitation of CVE-2026-69243 requires an environment where an aiohttp server is deployed behind a reverse proxy that supports HTTP/1.1 pipelining and WebSocket connections. The attacker must target an endpoint on the backend aiohttp server that is programmed to reject WebSocket upgrades. This scenario commonly occurs when WebSocket routes require authentication tokens or specific headers that the attacker intentionally omits or invalidates.\n\nTo perform the attack, the attacker transmits a crafted HTTP/1.1 request designed to look like a WebSocket upgrade to the reverse proxy. This request includes a Content-Length header specifying the size of a smuggled request payload contained in the body. The reverse proxy reads the request and the accompanying body, forwarding the entire stream to the backend server. The vulnerable backend aiohttp parser parses the headers, immediately switches to the upgraded state, and ignores the body. The application handler then processes the request and rejects the upgrade because of the invalid headers, sending an HTTP 403 response.\n\nBecause the backend failed to read the body, the smuggled request bytes remain in the TCP read buffer. When the backend server prepares to read the next pipelined HTTP request on the persistent connection, it reads these buffered bytes and executes the smuggled command. The diagram below illustrates this flow of execution:\n\nmermaid\ngraph LR\n Attacker["Attacker"] -->|"1. Sends Upgrade Request with Body"| Proxy["Reverse Proxy"]\n Proxy -->|"2. Forwards entire payload"| Backend["aiohttp (< 3.14.2)"]\n Backend -->|"3. Sets upgraded=True prematurely"| Parser["Parser State"]\n Backend -->|"4. App rejects upgrade, returns 403"| Client["Connection Left Open"]\n Parser -->|"5. Leftover body bytes remain in socket"| Buffer["Socket Read Buffer"]\n Backend -->|"6. Reads next request from socket"| Smuggled["Smuggled HTTP Request Executed"]\n\n\nSince the smuggled request appears to originate from the connection established between the trusted reverse proxy and the backend, it bypasses any access control rules, path restrictions, or security filters implemented solely at the reverse proxy layer.
The impact of successful exploitation is critical for applications that rely on frontend reverse proxies for access control, path-based routing, or request filtering. By successfully smuggling requests, an unauthenticated network attacker can execute arbitrary HTTP methods on the backend server. This allows them to access restricted administrative endpoints (e.g., /admin), perform unauthorized data modifications, or retrieve sensitive configuration resources.\n\nThe vulnerability is classified as Medium severity with a CVSS v4.0 score of 6.3. The vector string is CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N. This score reflects the high complexity (AC:H) required to successfully align the socket states and exploit timing characteristics, coupled with low integrity impacts (VI:L). There is no direct confidentiality or availability impact defined in the CVSS vector, although real-world consequences depend heavily on the specific endpoints exposed by the backend application.\n\nCurrently, the exploit maturity of this vulnerability is classified as 'unproven' or 'none'. No weaponized exploit payloads or public automated scanning templates have been identified in the wild, and the vulnerability is not listed in the CISA Known Exploited Vulnerabilities (KEV) catalog. However, because the public regression tests serve as a structural blueprint, security teams must assume that custom exploit chains can be developed by sophisticated actors.
The primary remediation for CVE-2026-69243 is to upgrade the aiohttp dependency to version 3.14.2 or higher. This update introduces the deferred protocol upgrade state logic, ensuring that the request body is always consumed prior to switching protocols. This completely eliminates the socket state desynchronization vector.\n\nIf upgrading the library is not immediately feasible, organizations must deploy defensive configurations at the reverse proxy or Web Application Firewall (WAF) layer. A highly effective mitigation is to block any incoming HTTP request that contains both a WebSocket upgrade header and a request body. Because standard WebSocket handshakes (RFC 6455) are GET requests and do not contain bodies, any upgrade request containing a Content-Length greater than zero or a Transfer-Encoding: chunked header should be treated as anomalous and rejected at the perimeter.\n\nAdditionally, security teams can implement strict connection management policies on the reverse proxy. Disabling HTTP keep-alive or connection reuse for backend connections that handle WebSocket handshakes prevents pipelined request processing. If the connection is closed immediately after the backend returns an error response, the buffered smuggled bytes will be discarded along with the closed socket, neutralizing the attack path.
CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
aiohttp aio-libs | < 3.14.2 | 3.14.2 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-444 |
| Attack Vector | Network (AV:N) |
| CVSS Vector | CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N |
| Exploit Status | poc |
| KEV Status | No |
| Affected Component | HTTP Parser |
The application parses or interprets HTTP requests in a way that is inconsistent with a proxy or other intermediate device, enabling request smuggling attacks.
CVE-2026-59881 is a protocol compliance and input validation vulnerability in the client-side WebSocket implementation of the aiohttp asynchronous HTTP client/server framework for Python. Prior to version 3.14.2, the framework's parser unexpectedly accepts and attempts to decompress frames containing the RSV1 bit, even when the permessage-deflate extension has not been negotiated during the initial WebSocket handshake. This violation of RFC 6455 allows a malicious or compromised server to bypass client configuration, forcing decompression routines that can lead to high CPU and memory consumption, resulting in a denial-of-service condition.
A vulnerability in the Guzzle HTTP client allows session identifiers, auth tokens, or cookies to be leaked to unauthorized hosts due to incorrect cookie domain validation of noncanonical IPv4 host formats. Guzzle failed to recognize octal, hexadecimal, and percent-encoded IP addresses as IP literals, treating them as standard domains and incorrectly extending their scope to subdomains.
CVE-2026-69246 is a host validation bypass vulnerability in the Guzzle PHP HTTP client. The flaw resides in Guzzle's core HTTP transport handlers (cURL and PHP stream wrappers). Under specific conditions, a parser differential occurs between the host validation layer and the underlying network transport library (e.g., libcurl), allowing remote attackers to bypass SSRF filters, proxy routing rules, and redirect protections via crafted noncanonical URI representations.
A side-channel vulnerability in pyca/cryptography (versions 44.0.0 through 49.9.9) allows unauthenticated remote attackers to expose a Bleichenbacher oracle. This flaw exists within the PKCS#7 decryption module (specifically pkcs7_decrypt_der, pkcs7_decrypt_pem, and pkcs7_decrypt_smime) during Content Encryption Key (CEK) decryption when using RSA PKCS#1 v1.5 padding. Differences in error classification and symmetric execution timing allow an attacker to reconstruct plaintext keys.
An uncontrolled resource consumption vulnerability (CWE-400) exists in the python-cryptography library's Rust-based X.509 verification engine. The flaw allows unauthenticated remote attackers to trigger severe CPU exhaustion and Denial of Service (DoS) by supplying specially crafted certificate chains containing duplicate self-signed certificates, forcing the recursive path builder into an exponential state-search loop.
An improper certificate validation vulnerability (CWE-295) in the Rust-based X.509 verification engine of python-cryptography allows wildcard Subject Alternative Names (SANs) to bypass permitted Name Constraints. This enables an attacker to construct certificates that escape the restricted scope of a subordinate Certificate Authority (CA) and successfully authenticate against vulnerable client installations. The vulnerability is tracked as CVE-2026-69248 and GHSA-m2h6-j472-rp4c, with a CVSS v4.0 base score of 6.9.