
ゼロバイトのQUICパケット1つで、HAProxyのバックエンド接続プールを非同期化し、無関係なユーザー間でHTTPリクエストを密輸できます。これは、まったく異なるフロントエンドプロトコルを使用しているユーザー間でも発生します。
完全な記事はこちら : https://r3verii.github.io/cve/2026/04/14/haproxy-h3-standalone-fin-smuggling.html
HAProxy の HTTP/3 実装における脆弱性により、攻撃者は実際のボディサイズと一致しない Content-Length ヘッダーを含む HTTP リクエストを送信できます。HAProxy はこの不正なリクエストを、宣言された Content-Length を持つもののボディバイトがゼロの状態で HTTP/1.1 経由でバックエンドに転送します。バックエンドが早期レスポンス(例:301 リダイレクト)を送信し、TCP 接続から保留中のボディをドレインすると、その接続上の次の HTTP リクエストに属するバイトを消費します — これは別のユーザーからのリクエストである可能性があります。
これにより、HAProxy のバックエンド接続プールを介したクロスユーザー HTTP リクエストスマグリングが発生します。
影響を受けるバージョン: QUIC/H3 サポート付き HAProxy(USE_QUIC=1)。HAProxy 3.0.18 でテスト済み。
必要な設定: (非デフォルトですが、本番環境では一般的)
http-reuse always3 つのサービスを含む Docker Compose:
http-reuse always)/photos ディレクトリで autoindex on、/status は 200 を返す# 1. ラボを起動(初回の HAProxy ビルドには約 10 分かかります)
cd poc/
docker compose up -d --build
# 2. PoC を連続モードで実行
docker exec -it poc-client python3 poc.py --target haproxy --port 10002 --interval 3
# 3. ブラウザから https://<host>:10002/status にアクセス
# (自己署名証明書を受け入れ、Chrome では --ignore-certificate-errors を使用)
# 繰り返し更新してください。レスポンスの約 50% が 400 Bad Request になります。
# 4. PoC を停止(Ctrl+C)。すべてのブラウザレスポンスが正常(200)に戻ります。
docker exec -it poc-client python3 poc.py --target haproxy --port 10002 --once
期待される出力:
[1] ポイズンリクエストを送信(H3/QUIC)...
-> 301 を受信。バックエンド接続が保留中のボディドレインとともにプールされました。
[2] HAProxy が接続をプールするまで 0.5 秒待機...
[3] 別の QUIC 接続から被害者の GET /status を送信...
-> レスポンス: HTTP 400
[!] スマグリング確認
[!] 別の接続の被害者が 200 ではなく 400 を受信
[!] バックエンドが被害者のリクエストをポイズン POST のボディとして解析