HAProxy HTTP/3 -> HTTP/1 Desync: Cross-Protocol Smuggling via a Standalone QUIC FIN (CVE-2026-33555)

HAProxy HTTP/3 -> HTTP/1 Desync: Cross-Protocol Smuggling via a Standalone QUIC FIN (CVE-2026-33555)

James Kettle’s work on request smuggling has always inspired me. I’ve followed his research, watched his talks at DEFCON and BlackHat, and spent time experimenting with his labs and tooling.

Coming from a web security background, I’ve explored vulnerabilities both from a black-box and white-box perspective — understanding not just how to exploit them, but also the exact lines of code responsible for issues like SQLi, XSS, and broken access control.

Request smuggling, however, always felt different. It remained something I could detect and exploit… but never fully trace down to its root cause in real-world server implementations.

A few months ago, I decided to go deeper into networking and protocol internals, and now, months later, I can say that I “might” have figured out how the internet works😂
This research on HAProxy (HTTP/3) is the result of that journey — finally connecting the dots between protocol behavior and the actual code paths leading to the bug.


0. What this post is

This is the writeup of a vulnerability I found in HAProxy and reported through coordinated disclosure. The HAProxy team confirmed the issue and it was assigned CVE-2026-33555.

Most HTTP smuggling writeups jump straight to the exploit. This one starts from the ground up: what QUIC packets actually look like, how HAProxy processes them layer by layer, and why a single missing validation check in one fast-path creates a cross-user request smuggling primitive. If you’ve never looked at QUIC internals before, you should still be able to follow.

If you just want the PoC: jump to section 4.


1. Context

I’ve been spending time this year on HTTP/2 and HTTP/3 attack surface in reverse proxies — specifically how protocol translation boundaries (H3→H1, H2→H1) can introduce semantic mismatches that neither side catches. HAProxy 3.x with USE_QUIC=1 was a natural target: a relatively young, hand-rolled H3 implementation bridging QUIC stream semantics to HTTP/1.1 wire format. Two fundamentally different framing models, stitched together.

The research was done almost entirely with Claude Code (Opus 4.6), which turned out to be remarkably effective at navigating a C codebase of this size (~8000 lines across the relevant mux files). I don’t know C deeply, and I certainly couldn’t hold the full architecture of HAProxy in my head. But I could ask precise questions about code paths, and Claude Code would trace them through function calls, line by line, and explain what each piece did. The vulnerability was found this way: not by fuzzing, but by reading the source and asking “does this validation always run?”

The rest of this post is structured so that you can follow the whole thing with zero prior knowledge of QUIC. If you already know QUIC internals, section 2 will be review. If you don’t, it’s the foundation you need — the bug only makes sense once you understand why QUIC’s FIN bit lives at a completely different layer than HTTP/2’s END_STREAM flag, and why that layering choice gives an attacker packet-level control that HTTP/2 simply doesn’t expose.

2. The networking foundation

Read the full post at https://r3verii.github.io/cve/2026/04/14/haproxy-h3-standalone-fin-smuggling.html