It’s like a postal clerk who stamps “20 pages enclosed” on an envelope after reading the cover letter, before counting what’s actually inside
CVE-2026-90678 is an HTTP request smuggling flaw in HAProxy’s HTTP/3-to-HTTP/1.1 gateway path. When a client sends an HTTP/3 request *without* a Content-Length header, the QUIC multiplexer trusts the length declared in the DATA frame header and emits it verbatim as the HTTP/1.1 Transfer-Encoding: chunked size — before the payload bytes actually arrive. An attacker who declares more payload than they deliver and then closes the QUIC stream leaves the backend connection in a desynchronized state: the announced chunk is larger than the bytes written, and HAProxy returns the poisoned connection to the idle pool. The flaw was introduced in 3.3-dev10 and affects all releases from 3.3.0 through 3.4.4 and 3.5-dev1 through 3.5-dev5. Versions 3.2.x and earlier are unaffected.
The vendor’s HIGH / 7.5 score is well-calibrated. The AC:H reflects the non-deterministic race with the backend connection pool (the advisory notes exploitation “succeeds in a majority of but not all trials”). The S:C correctly flags that the victim is a *different* user whose request lands on the poisoned connection. The C:N is debatable — stolen Authorization headers are consumed as the attacker’s request body rather than reflected back, but with careful request crafting the attacker can arrange to receive responses containing that data. The score also does *not* fully account for the major narrowing prerequisite: HAProxy must be compiled with USE_QUIC=1 *and* configured with a QUIC bind listener. In enterprise deployments, HTTP/3 on HAProxy 3.3+ is still an early-adopter configuration, meaningfully shrinking the exposed population.
5 steps from start to impact.
Identify a QUIC-enabled HAProxy frontend
Initial packets or Alt-Svc headers advertising h3. Shodan and Censys index QUIC endpoints; automated scanning is straightforward.- HAProxy 3.3.0–3.4.4 or 3.5-dev1–3.5-dev5 compiled with USE_QUIC=1
- QUIC bind listener configured on an internet-facing frontend
- Most enterprise HAProxy deployments still run 2.x LTS branches or 3.x without QUIC enabled
- HTTP/3 on HAProxy is an opt-in, early-adopter feature requiring explicit compilation and configuration
product:haproxy ssl.alpn:h3; GreyNoise can surface opportunistic QUIC scanning activity.Send crafted HTTP/3 request with no Content-Length
Content-Length header. In the DATA frame header, the attacker declares a payload length *larger* than the bytes they intend to deliver — e.g., declaring 4096 bytes but sending only 100. The QUIC multiplexer trusts the DATA frame length and emits it as the HTTP/1.1 chunk size before the payload arrives.- Backend must use HTTP/1.1 with chunked transfer coding on a reused connection (HAProxy default)
- Minimal — the default backend configuration satisfies this. HTTP/2 backends are not affected.
Terminate stream early to desynchronize backend
- HAProxy must be using connection pooling to backends (enabled by default)
- The race between stream close and pool return is non-deterministic, but the advisory confirms success in a majority of trials and free retries
Smuggle a request on the poisoned connection
Authorization tokens. The attacker’s smuggled request bypasses HAProxy’s frontend ACLs entirely because HAProxy never parses it. The victim’s request is consumed as the body of the attacker’s request.- A legitimate user must send a request that lands on the poisoned connection before timeout
- High-traffic deployments make this near-certain
- On low-traffic backends the window may close before a victim request arrives
Exfiltrate stolen credentials or access unauthorized resources
http-request deny ACL, achieving authorization bypass. Blind attempts still disrupt availability by corrupting request streams.- Attacker must have a way to observe the response or know backend topology for targeted exploitation
- Requires knowledge of backend routing for meaningful credential theft; blind smuggling still causes availability disruption
The supporting signals.
| In-the-wild status | No known exploitation. Not listed in CISA KEV. No GreyNoise tags or campaign tracking as of 2026-09-20. |
|---|---|
| Proof-of-concept | No public PoC identified. No dedicated GitHub repos, Nuclei templates, or Metasploit modules. The advisory describes the attack in enough detail that a skilled researcher could reproduce it with a custom QUIC client (e.g., aioquic). |
| EPSS | 0.52% probability of exploitation in the next 30 days — low, consistent with the QUIC prerequisite narrowing the attacker population. |
| KEV status | Not listed. No federal mandate for accelerated patching. |
| CVSS vector | CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:H/A:L — network-reachable, high complexity (race condition), no privileges, changed scope (cross-user impact), high integrity (ACL bypass + smuggling), low availability. |
| Affected versions | HAProxy 3.3.0 – 3.4.4 (stable/LTS) and 3.5-dev1 – 3.5-dev5 (development). Introduced in 3.3-dev10. Versions ≤ 3.2.x unaffected. |
| Fixed versions | 3.4.5, 3.3.15, 3.5-dev6 (expected — fix committed as 86a4ebc761a). Distro backports: NixOS PRs #563806 / #565055 merged; Ubuntu tracker open. |
| Scanning / Exposure | Shodan indexes HAProxy instances; QUIC-enabled subset identifiable via Alt-Svc: h3 headers or UDP/443 probes. HTTP/3 adoption is ~35% globally but primarily via CDNs (Cloudflare, Fastly), not self-hosted HAProxy. Shadowserver publishes an Accessible QUIC Report. |
| Disclosure date | 2026-09-13 (public advisory). |
| Researcher / Org | Not publicly attributed. Likely discovered internally by HAProxy Technologies during QUIC development. |
Why this verdict
- Baseline is fair: HAProxy is a canonical network edge component. A request-smuggling bug in a load balancer is inherently high-impact because it undermines the security boundary between the internet and backend services.
- Friction — QUIC prerequisite: The chain requires HAProxy compiled with
USE_QUIC=1and configured with a QUICbindlistener. Among enterprise HAProxy 3.3+ deployments, QUIC enablement is an early-adopter configuration — likely 15–25% of the affected version’s installed base. The CVSSAC:Hcaptures the connection-pool race but does *not* fully capture this configuration prerequisite. - Friction — race condition: Exploitation is non-deterministic but retriable. The advisory states success in “a majority of but not all trials.” Minimal downward pressure.
- Friction — backend topology knowledge: Meaningful exploitation (credential theft vs. blind disruption) requires the attacker to know enough about backend routing. Blind attempts still cause availability disruption but not targeted data theft.
- Role multiplier — Network edge appliance (HIGH-value role): HAProxy is *canonically* a network edge component. The majority of installs sit at the perimeter or in a critical L7 routing position. When request smuggling succeeds on an edge proxy, the blast radius is tenant-scale to multi-user-scale: ACL bypass exposes protected backends, and credential theft via consumed
Authorizationheaders affects all users whose requests land on poisoned connections. The chain succeeds in this role; the floor is HIGH. - Role multiplier — Internal service mesh (typical role): HAProxy used for internal east-west traffic without QUIC frontends is entirely unaffected by this CVE. No adjustment.
Why not higher?
The impact caps at tenant/multi-user credential theft and ACL bypass — serious, but not domain takeover, fleet compromise, or supply-chain pivot. The QUIC prerequisite meaningfully narrows the exploitable population to early adopters of HTTP/3 on HAProxy 3.3+. No PoC exists, no in-the-wild exploitation is documented, EPSS is 0.52%, and it is not KEV-listed.
Why not lower?
Request smuggling on a load balancer is a well-understood, high-impact attack class. HAProxy’s canonical role at the network edge means a successful chain violates the primary security boundary for all traffic behind it. The S:C (changed scope / cross-user impact) and I:H (integrity — total ACL bypass) combine with unauthenticated, remote, no-UI-required access to keep this firmly above MEDIUM.
What to do — in priority order.
- Disable QUIC/HTTP/3 listeners immediately — Remove
quic@/quic4@/quic6@bind directives from HAProxy frontend config and reload. Eliminates the attack surface entirely with zero functional impact for orgs that don’t need HTTP/3. Deploy within 30 days per the noisgate mitigation SLA for HIGH findings. - Pin backend protocol to HTTP/2 — Set
server ... proto h2in backend configuration. The desync only affects HTTP/1.1 chunked transfer coding on reused connections; HTTP/2 multiplexing uses a different framing model not susceptible to this mismatch. - Disable backend connection reuse for QUIC frontends — Add
http-reuse neverto backends served by QUIC-enabled frontends. Prevents the poisoned connection from being returned to the idle pool. Performance impact: higher backend connection churn, increased latency under load. - Deploy patched version (3.4.5 / 3.3.15 / 3.5-dev6) — Apply the vendor fix containing commit 86a4ebc761a278838e8cb06f3a292282ba704c65. This is the definitive remediation. Deploy within 180 days per the noisgate remediation SLA for HIGH findings.
- Monitor backend logs for chunked-encoding anomalies — Alert on HTTP 400 errors from backends correlating with malformed Transfer-Encoding: chunked requests. Detection backstop, not prevention.
- WAF in front of HAProxy — The smuggled request bypasses HAProxy’s own HTTP analysis. An upstream WAF inspecting HTTP/3 framing internals at the QUIC layer is effectively non-existent in current products. The WAF sees the clean outer request, not the smuggled payload.
- Rate limiting on the frontend — The attack uses normal-looking HTTP/3 requests with no volumetric signature. Each attempt is a single request.
- TLS inspection / SSL decryption — QUIC encrypts end-to-end. Network-layer TLS inspection appliances cannot decrypt QUIC traffic without terminating it, which is what HAProxy itself is doing.
- IP-based blocking — The attacker needs only a single request to poison a connection; there is no reconnaissance pattern to block by IP reputation.
Crowdsourced verification payload.
Run this on each HAProxy host as root (or any user with read access to the HAProxy binary and config file). Example: sudo bash check_cve_2026_90678.sh or sudo bash check_cve_2026_90678.sh /etc/haproxy/haproxy.cfg.
#!/usr/bin/env bash
# check_cve_2026_90678.sh — Detect exposure to CVE-2026-90678
# HAProxy HTTP/3 request smuggling via QUIC DATA frame length mismatch
# Run on: target HAProxy host | Privileges: read access to haproxy binary + config
# Output: VULNERABLE / PATCHED / UNKNOWN
set -euo pipefail
CONFIG="${1:-/etc/haproxy/haproxy.cfg}"
HAPROXY_BIN="$(command -v haproxy 2>/dev/null || echo '')"
if [[ -z "$HAPROXY_BIN" ]]; then
echo "UNKNOWN — haproxy binary not found in PATH"
exit 2
fi
# Get version
VERSION_RAW=$($HAPROXY_BIN -v 2>/dev/null | head -1)
VERSION=$(echo "$VERSION_RAW" | grep -oP '\d+\.\d+\.\d+' | head -1)
DEV_VERSION=$(echo "$VERSION_RAW" | grep -oP '\d+\.\d+-dev\d+' | head -1)
if [[ -z "$VERSION" && -z "$DEV_VERSION" ]]; then
echo "UNKNOWN — could not parse HAProxy version from: $VERSION_RAW"
exit 2
fi
# Check if compiled with QUIC support
QUIC_SUPPORT=$($HAPROXY_BIN -vv 2>/dev/null | grep -ci 'USE_QUIC=1' || true)
# Check if config has QUIC bind listeners
QUIC_BIND=0
if [[ -f "$CONFIG" ]]; then
QUIC_BIND=$(grep -cE 'bind\s+.*quic[46]?@' "$CONFIG" 2>/dev/null || true)
fi
# Version comparison helper
verlte() { printf '%s\n' "$1" "$2" | sort -V | head -n1; }
VULN_VERSION=0
if [[ -n "$DEV_VERSION" ]]; then
# Development version: 3.5-dev1 through 3.5-dev5 are vulnerable
DEV_MAJOR=$(echo "$DEV_VERSION" | grep -oP '\d+\.\d+' | head -1)
DEV_NUM=$(echo "$DEV_VERSION" | grep -oP 'dev\K\d+')
if [[ "$DEV_MAJOR" == "3.5" && "$DEV_NUM" -ge 1 && "$DEV_NUM" -le 5 ]]; then
VULN_VERSION=1
fi
elif [[ -n "$VERSION" ]]; then
MAJOR_MINOR=$(echo "$VERSION" | grep -oP '\d+\.\d+')
if [[ "$MAJOR_MINOR" == "3.4" ]]; then
PATCH=$(echo "$VERSION" | cut -d. -f3)
[[ "$PATCH" -le 4 ]] && VULN_VERSION=1
elif [[ "$MAJOR_MINOR" == "3.3" ]]; then
PATCH=$(echo "$VERSION" | cut -d. -f3)
[[ "$PATCH" -le 14 ]] && VULN_VERSION=1
fi
fi
# Report
echo "HAProxy: ${VERSION:-$DEV_VERSION} | QUIC compiled: $([[ $QUIC_SUPPORT -gt 0 ]] && echo YES || echo NO) | QUIC bind: $([[ $QUIC_BIND -gt 0 ]] && echo YES || echo NO)"
if [[ "$VULN_VERSION" -eq 0 ]]; then
echo "PATCHED — version ${VERSION:-$DEV_VERSION} is not in the affected range (3.3.0-3.4.4 / 3.5-dev1-dev5)"
exit 0
fi
if [[ "$QUIC_SUPPORT" -eq 0 ]]; then
echo "PATCHED — version is in range but binary not compiled with USE_QUIC=1; not exploitable"
exit 0
fi
if [[ "$QUIC_BIND" -eq 0 ]]; then
echo "PATCHED — version and binary affected but no QUIC bind listeners in $CONFIG; not exploitable unless config differs"
exit 0
fi
echo "VULNERABLE — HAProxy ${VERSION:-$DEV_VERSION} with QUIC support enabled and QUIC bind listeners active"
exit 1What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.