Pure HTTP/2 was designed to remove many of the boundary ambiguities that make classic HTTP/1.1 request smuggling possible. The real problem returns when a front-end component accepts HTTP/2 from the client but downgrades it to HTTP/1.1 on the back-end. In that mixed environment, attacker-controlled HTTP/2 input can shape the downgraded HTTP/1.1 request in unsafe ways, leading to desynchronization, request tunneling, ACL bypass, or cache poisoning. These notes cover the downgrade model, H2.CL, H2.TE, CRLF injection, and h2c smuggling.
HTTP/2 is binary and uses explicit length tracking for request components. Instead of relying on textual delimiters like \r\n between headers and bodies, the protocol carries clear size information for fields and frames.
In a truly end-to-end HTTP/2 environment, this removes much of the classic ambiguity around request boundaries.
At a high level, an HTTP/2 request consists of:
:method, :path, :scheme, and :authority,The main security implication is that sizes are unambiguous at the HTTP/2 layer itself.
The danger appears when the front-end speaks HTTP/2 to the client but converts the request into HTTP/1.1 for the back-end. This is HTTP/2 downgrading.
If the downgrade logic copies dangerous headers or interprets binary data unsafely, the resulting HTTP/1.1 request can become desynchronized even though the original frontend request was valid HTTP/2.
In a downgrade scenario:
Ideally, one frontend request maps cleanly to one backend request. Unsafe translation breaks that assumption.
H2.CL happens when a malicious Content-Length header is accepted from the HTTP/2 side and copied into the downgraded HTTP/1.1 request. Even though HTTP/2 does not need that header for body delimitation, the backend HTTP/1.1 server may trust it.
POST / HTTP/2
:authority: target
:path: /
:method: POST
content-length: 0
HELLO
After downgrade, the backend may believe the first request has no body, leaving the remaining bytes queued to corrupt the next request on the connection.
H2.TE works the same way with an injected Transfer-Encoding: chunked header. If the proxy passes that header to the backend HTTP/1.1 connection and the backend honors it, a zero-length chunk can terminate the request early and leave poison bytes behind.
The underlying issue is still the same: safe HTTP/2 input creates unsafe HTTP/1.1 interpretation after downgrade.
HTTP/2 is binary, so special characters such as carriage return and line feed can be placed into fields in ways that would not normally be representable safely in plain HTTP/1.1 text editing. When downgrade code translates that data into HTTP/1.1, injected \r\n can become real header separators.
That means attacker input in an HTTP/2 header can potentially add a new header or even start a second HTTP/1.1 request after downgrade.
Not every HTTP/2 smuggling case lets you poison another user’s connection. Some front-ends give each user a distinct backend connection. In that case, you may still smuggle requests through your own tunnel, but you cannot directly desync another user’s stream.
This is usually called request tunneling. It is still useful for bypassing front-end ACLs, leaking internal headers, or poisoning caches under the right conditions.
If the proxy inserts headers before sending requests to the backend, CRLF-based request tunneling can sometimes be used to reflect those hidden headers back through an application feature that echoes request data.
This is often the first step before building a more targeted tunneled request that must satisfy backend expectations the frontend normally hides from you.
If the front-end proxy blocks access to a sensitive path like /admin but allows an innocuous path like /hello, a tunneled HTTP/2 request can sometimes use the allowed path externally while causing the backend to process a second, forbidden request internally.
From the proxy’s perspective, you requested only an allowed resource. From the backend’s perspective, the forbidden request still arrived.
Even if you cannot directly affect another user’s live backend connection, you may still poison shared caches. If the proxy associates the wrong backend response with a cache key after a tunneled split, later users may receive attacker-chosen content from cache.
That can escalate to broad client-side impact when the poisoned resource is JavaScript or another shared asset.
Servers can offer multiple HTTP versions on the same port. For encrypted connections, HTTP/2 is usually negotiated through ALPN using the h2 identifier. Historically, cleartext HTTP/2 upgrades used h2c.
While browsers rarely use cleartext HTTP/2 in practice now, back-end and proxy support for h2c can still matter in niche or legacy setups.
With h2c smuggling, the attacker uses an HTTP/1.1 upgrade request to convince the front-end to tunnel HTTP/2 traffic through to the back-end. If the proxy forwards the upgrade rather than handling it safely, later requests inside that tunnel may bypass frontend inspection entirely.
This usually enables request tunneling rather than cross-user backend poisoning, but that can still be enough to bypass front-end path restrictions.
Content-Length or Transfer-Encoding into downgraded requests,