H2 to H1 Desync: Hunting HTTP/2 Downgrade Smuggling Bugs
HTTP/2 has explicit frame lengths, so classic CL-versus-TE ambiguity should disappear. Then the CDN translates H2 into HTTP/1.1 for an older origin and rebuilds textual headers. That downgrade is where the old bug returns wearing a newer protocol.
The key question is simple: does the front end derive the H1 body length from H2 frames, or does it trust a user-supplied content-length header?
Start by proving the downgrade
Burp Repeater lets you switch a request to HTTP/2 and inspect pseudo-headers. Compare behavior for an H2 request containing a forbidden or mismatched content-length. A strict edge rejects it. A risky edge forwards an H1 request whose CL conflicts with the framed H2 body.
Signals of an H1 back end include H1-style error messages, hop-by-hop header behavior, or differences between H2 and H1 requests to the same endpoint. None proves a downgrade alone. Treat them as reasons to run a bounded H2.CL timing test.
With H2.CL, send fewer DATA bytes than the supplied content length. The front end knows the stream ended; the downgraded origin may wait for the missing bytes. Measure against a matching request with correct length. A stable delay linked to the mismatch is your first useful artifact.
H2 header injection is a separate path
HTTP/2 header names and values are encoded, not delimited by CRLF on the wire. A careless downgrade function may copy a value containing into an H1 header block. If the edge fails to reject those characters, one H2 header can become two H1 headers—or even terminate the request.
Try a harmless injected header such as X-Canary: h2test and observe it only on an endpoint that reflects request metadata or on your own controlled follow-up. Do not inject Host, authentication, or cache directives until a canary proves the translation flaw and scope permits escalation.
Other useful checks:
- Send duplicate pseudo-header-like values through normal headers and watch normalization.
- Compare requests with H2
content-length: 0plus DATA frames. - Test front-end-only headers that should be removed during downgrade.
- Repeat over fresh and reused connections to learn whether the queue state survives.
One request at a time. Desync bugs become unsafe when automation races hundreds of streams through a shared origin pool.
A bounty-grade demonstration
Show the initial H2 request, the translated behavior, and a second request you own receiving a deterministic effect. A poisoned 404 path, an injected benign header, or a response mix-up between two test accounts is strong. Explain stream IDs and connection reuse in the report; video alone often hides the detail engineers need.
Severity follows the reachable consequence. Front-end authorization bypass, cache poisoning, and cross-user response capture carry weight. A malformed H2 request that merely receives 400 does not.
Recommend strict H2 validation, deriving H1 CL only from decoded DATA length, rejecting CR/LF in values, and using H2 to the origin where feasible. PortSwigger's HTTP/2 downgrade research documents H2.CL and request splitting. CWE-444 covers inconsistent interpretation, and OWASP's testing guidance supports controlled, low-volume verification.