← Back to Feed CACHED · 2026-09-20 10:54:40 · CACHE_KEY CVE-2026-90678
CVE-2026-90678 · CWE-130 · Disclosed 2026-09-13

HAProxy 3.3.0 through 3.4.4 and in 3.5-dev1 through 3.5-dev5.

ASSESSED — NOISGATE
Vendor
Reassessed
Verdict:
Do you agree?
01 · The Real Story

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.

"HTTP/3 request smuggling on HAProxy is real but gated behind QUIC — patch edge proxies first."
02 · The Attack Path

5 steps from start to impact.

STEP 01

Identify a QUIC-enabled HAProxy frontend

The attacker locates an HAProxy instance accepting HTTP/3 connections on UDP/443. This can be fingerprinted via QUIC Initial packets or Alt-Svc headers advertising h3. Shodan and Censys index QUIC endpoints; automated scanning is straightforward.
Conditions required:
  • 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
Where this breaks in practice:
  • 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
Detection/coverage: Shodan dork product:haproxy ssl.alpn:h3; GreyNoise can surface opportunistic QUIC scanning activity.
STEP 02

Send crafted HTTP/3 request with no Content-Length

The attacker opens an HTTP/3 stream and sends a request omitting the 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.
Conditions required:
  • Backend must use HTTP/1.1 with chunked transfer coding on a reused connection (HAProxy default)
Where this breaks in practice:
  • Minimal — the default backend configuration satisfies this. HTTP/2 backends are not affected.
Detection/coverage: Deep-packet inspection on QUIC is rare; WAFs typically don’t inspect HTTP/3 framing internals.
STEP 03

Terminate stream early to desynchronize backend

After sending fewer bytes than declared, the attacker closes the QUIC stream (FIN or RST_STREAM). HAProxy has already written the oversized chunk header to the backend but cannot deliver the remaining bytes, leaving the HTTP/1.1 connection desynchronized. HAProxy then returns this poisoned connection to the idle pool.
Conditions required:
  • HAProxy must be using connection pooling to backends (enabled by default)
Where this breaks in practice:
  • The race between stream close and pool return is non-deterministic, but the advisory confirms success in a majority of trials and free retries
Detection/coverage: Backend servers may log malformed chunked requests or connection resets, but these are often attributed to network noise.
STEP 04

Smuggle a request on the poisoned connection

When the next legitimate client’s request is dispatched over the desynchronized backend connection, the remaining “declared-but-undelivered” bytes are filled by the victim’s request — including their request line, headers, and 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.
Conditions required:
  • A legitimate user must send a request that lands on the poisoned connection before timeout
  • High-traffic deployments make this near-certain
Where this breaks in practice:
  • On low-traffic backends the window may close before a victim request arrives
Detection/coverage: Application-layer anomaly detection may flag unexpected request bodies or missing auth headers on backend logs.
STEP 05

Exfiltrate stolen credentials or access unauthorized resources

The smuggled request reaches a backend endpoint the attacker controls or can observe, carrying the victim’s credentials. Alternatively, the attacker routes the smuggled request to an internal endpoint denied by HAProxy’s http-request deny ACL, achieving authorization bypass. Blind attempts still disrupt availability by corrupting request streams.
Conditions required:
  • Attacker must have a way to observe the response or know backend topology for targeted exploitation
Where this breaks in practice:
  • Requires knowledge of backend routing for meaningful credential theft; blind smuggling still causes availability disruption
Detection/coverage: SIEM correlation of mismatched request-response pairs; backend application logging of unexpected request patterns.
03 · Intelligence Metadata

The supporting signals.

In-the-wild statusNo known exploitation. Not listed in CISA KEV. No GreyNoise tags or campaign tracking as of 2026-09-20.
Proof-of-conceptNo 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).
EPSS0.52% probability of exploitation in the next 30 days — low, consistent with the QUIC prerequisite narrowing the attacker population.
KEV statusNot listed. No federal mandate for accelerated patching.
CVSS vectorCVSS: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 versionsHAProxy 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 versions3.4.5, 3.3.15, 3.5-dev6 (expected — fix committed as 86a4ebc761a). Distro backports: NixOS PRs #563806 / #565055 merged; Ubuntu tracker open.
Scanning / ExposureShodan 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 date2026-09-13 (public advisory).
Researcher / OrgNot publicly attributed. Likely discovered internally by HAProxy Technologies during QUIC development.
04 · The Call

Final Verdict
= UNCHANGED to HIGH (7.5/10)

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=1 and configured with a QUIC bind listener. Among enterprise HAProxy 3.3+ deployments, QUIC enablement is an early-adopter configuration — likely 15–25% of the affected version’s installed base. The CVSS AC:H captures 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 Authorization headers 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.

05 · Compensating Control

What to do — in priority order.

  1. 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.
  2. Pin backend protocol to HTTP/2 — Set server ... proto h2 in 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.
  3. Disable backend connection reuse for QUIC frontends — Add http-reuse never to 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.
  4. 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.
  5. 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.
What doesn't work
  • 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.
06 · Verification

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.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/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 1
07 · Sources

Peer Review

What defenders are saying.

Submit a review attribution: handle + country only
0 flags selected · stored anonymously
Validation Results

Crowdsourced verification outputs.

Results submitted by users who ran the verification payload against their environment.