Aug 6, 2026·5 min read·77 visits
The 'h2' Python library fails to reject HTTP/2 streams containing multiple 'Host' headers. If an intermediary proxy downgrades these streams to HTTP/1.1, the duplicate headers are forwarded, triggering HTTP Request Smuggling.
A protocol-parsing vulnerability in the pure-Python HTTP/2 library 'h2' (versions <= 4.4.0) allows unauthenticated remote attackers to perform HTTP Request Smuggling (CWE-444). The vulnerability exists because the library does not validate the uniqueness of 'Host' headers in incoming HTTP/2 request streams. When an upstream gateway parses such requests and downgrades them to HTTP/1.1 for internal backend servers, the resulting stream contains duplicate Host headers, which leads to parsing inconsistency and potential bypass of security filters.
The pure-Python HTTP/2 protocol implementation library h2 is widely deployed as a core dependency in Python web applications, ASGI servers, and microservice proxy frameworks. The library is responsible for parsing low-level HTTP/2 frames, including HEADERS frames, and ensuring compliance with HTTP/2 and generic HTTP standards. Under standard configurations, the library acts as the front-line parser for incoming external network traffic before passing structured requests to upper-layer applications.
The vulnerability is classified under CWE-444: Inconsistent Interpretation of HTTP Requests ('HTTP Request/Response Smuggling'). The flaw lies in the handling of HTTP/2 headers that map directly to HTTP/1.1 connection concepts. Because HTTP/2 mandates lower-case header names and relies primarily on pseudo-headers like :authority for routing, standard HTTP/1.1 header validation rules are sometimes overlooked or improperly implemented during transition phases.
When a downstream proxy downgrades incoming HTTP/2 connections to HTTP/1.1 to route them to legacy backends, the absence of duplicate Host header validation in h2 causes both headers to be serialized. The downstream systems then receive conflicting metadata regarding the target host, exposing the deployment to request smuggling vectors.
The root cause of this vulnerability is located within the _validate_host_authority_header generator function in src/h2/utilities.py. In an HTTP/2 context, request authorities are primarily defined by the :authority pseudo-header. However, for backward compatibility and transition purposes, RFC 9110 specifies that clients may also supply a standard host header. According to HTTP semantics, any individual request must contain at most a single host identifier.
Prior to version 4.4.1, h2 did not track whether a host header had already been processed when parsing incoming headers in a loop. The utility generator processed and yielded every b"host" byte-string identifier encountered. The parser updated a local variable host_header_val to the last value parsed, discarding earlier values without raising any protocol exception.
Because the parser allowed multiple instances of the host key to pass validation, an attacker could include multiple conflicting host headers within a single HEADERS frame. The state machine continued tracking the connection successfully, eventually yielding all the headers directly to the application layer.
To understand the implementation flaw, observe the original validation loop inside src/h2/utilities.py before the patch:
# Vulnerable implementation in h2 <= 4.4.0
def _validate_host_authority_header(headers: Iterable[Header]) -> Generator[Header, None, None]:
# ... initialization of variables ...
for header in headers:
if header[0] == b":authority":
authority_header_val = header[1]
elif header[0] == b"host":
# Missing: Verification that host_header_val is None
host_header_val = header[1]
yield headerThe patch introduced in version 4.4.1 (commit 292a40829feefda98c8509dcdbbb4a57af9bd6a6) inserts a explicit check to verify that host_header_val is unassigned before recording a new b"host" value:
# Patched implementation in h2 4.4.1
def _validate_host_authority_header(headers: Iterable[Header]) -> Generator[Header, None, None]:
# ... initialization of variables ...
for header in headers:
if header[0] == b":authority":
authority_header_val = header[1]
elif header[0] == b"host":
if host_header_val is not None:
msg = "Request header block has multiple Host headers."
raise ProtocolError(msg)
host_header_val = header[1]
yield headerThis modification ensures that if a duplicate b"host" header is encountered, h2 immediately raises a ProtocolError exception, shutting down the stream. This prevents the request from reaching the application layer or downstream servers.
The fix is complete because HTTP/2 mandates that header names must be entirely lowercase. The byte-string comparison header[0] == b"host" is sufficient to catch all validly formatted host headers. Any mixed-case variants (e.g., Host or HOST) are rejected at an earlier stage by the HTTP/2 frame decoder, preventing bypasses utilizing header name capitalization.
An attacker seeking to exploit this vulnerability must target an environment that uses a vulnerable h2 gateway downstream of an HTTP/1.1 backend infrastructure. The attack is structured as follows:
Request Crafting: The attacker sends an HTTP/2 request containing standard pseudo-headers and appends duplicate host headers. For instance:
:authority: valid-target.comhost: valid-target.comhost: restricted-internal-target.comProxy Processing: The front-end proxy uses the vulnerable h2 library to parse the request. Since h2 does not reject the duplicate headers, the request is passed through to the proxy's routing logic.
Connection Downgrading: The proxy converts the HTTP/2 stream to HTTP/1.1 to forward it to the backend server, serializing the headers as:
GET /admin HTTP/1.1
Host: valid-target.com
Host: restricted-internal-target.comInconsistent Interpretation: If the front-end proxy validated the request path /admin against valid-target.com (which is permitted), but the backend server prioritizes the second Host header (restricted-internal-target.com), the attacker successfully accesses restricted backend functions. This discrepancy allows bypass of host-based routing restrictions, web cache poisoning, or credential access.
The impact of CVE-2026-71554 depends heavily on the architecture of the hosting environment. If the Python application is directly exposed to the internet and does not downgrade connection protocols, the impact is negligible.
However, in cloud-native configurations where an ASGI server (such as Uvicorn or Hypercorn) or a custom gateway relies on h2 and sits behind an external load balancer, the threat is elevated. The vulnerability scores 5.3 (Medium) under CVSS v3.1, reflecting low confidentiality and integrity impact if exploited to bypass access control measures.
Additionally, because HTTP Request Smuggling can affect upstream caches, an attacker could potentially poison shared web caches, mapping legitimate pages to administrative resources, affecting other active users of the platform.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L| Product | Affected Versions | Fixed Version |
|---|---|---|
h2 python-hyper | < 4.4.1 | 4.4.1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-444 |
| Attack Vector | Network (AV:N) |
| CVSS v3.1 Score | 5.3 (Medium) |
| Impact | Low Integrity & Availability (Potential HTTP Request Smuggling) |
| Exploit Status | none (Theoretical / No public PoCs available) |
| KEV Status | Not Listed |
The software receives an HTTP request and parses it in an inconsistent way compared to upstream or downstream servers, allowing actors to 'smuggle' a request to the recipient without the intermediary's knowledge.
A path traversal vulnerability in datamodel-code-generator allows remote attackers to write or overwrite arbitrary files on the local host filesystem via a manipulated Protobuf schema containing malicious weak import paths.
PraisonAI is vulnerable to an arbitrary local file read vulnerability prior to version 4.6.78. The flaw is in the ContextGatherer component, where validation checks are executed only after files are parsed and appended to the context bundle, bypassing security constraints.
An algorithmic complexity vulnerability (CWE-770) in the Excelize library allows remote attackers to cause resource exhaustion (100% CPU usage) via a crafted Microsoft Excel spreadsheet. This occurs because the look-ahead row index parsing in Rows.Columns() fails to enforce upper boundary limits, enabling an out-of-bounds row index to trigger an infinite seek loop inside the Rows iterator.
An unauthenticated Server-Side Request Forgery (SSRF) vulnerability exists in Ghost CMS from version 6.54.1 to 6.65.0. The vulnerability stems from a validation bypass in the favicon resolution logic within the bookmark-fetching subsystem, which allows remote, unauthenticated attackers to trigger arbitrary HTTP requests to the local host and internal networks. This bypass circumvents the custom DNS-level IP blocklist controls configured globally in the application.
A resource allocation vulnerability (CWE-770) in lz4-java before version 1.11.4 allows an unauthenticated remote attacker to trigger CPU exhaustion and high garbage collection overhead by streaming empty concatenated LZ4 frames.
A Denial of Service (DoS) vulnerability exists in the yawkat fork of lz4-java prior to version 1.11.4. Under specific non-default configurations (stopOnEmptyBlock = false), parsing crafted streams with a large sequence of contiguous empty LZ4 blocks triggers uncontrolled recursion inside the LZ4BlockInputStream.refill() method, causing stack exhaustion and thread termination.