HTTP pipelining and keep-alive abuse
TL;DR: HTTP/1.1 pipelining (rare today) and connection keep-alive (universal) let multiple requests share one TCP/TLS connection. Bugs: request-response misalignment when upstream errors mid-response, frontend/backend disagreement on connection lifetime, response splitting via connection-reuse-aware payloads, state bleed between sequential requests on the same connection (proxy-side or origin-side), and HTTP/2/3 frame interleaving abuse. Related to but distinct from http-request-smuggling.
What it is
HTTP keep-alive: after a response, the TCP connection stays open for the next request (Connection: keep-alive in 1.0; default in 1.1). HTTP/2 multiplexes streams on one connection. HTTP/3 over QUIC further.
For connection-reuse vulnerabilities, the shared identity is the connection, not the request. Server state — proxy state — can bleed across requests on the same connection if mishandled.
Bug patterns
1. Connection-pool tenant bleed (proxy-side)
- Reverse proxy (HAProxy, nginx) pools backend connections.
- Connection from
req1to backend used forreq2from a different user. - If backend caches authentication on connection (rare but real for some protocols), bleed.
- More common in DB connection pools than HTTP, but HTTP proxy with custom auth (mTLS) can exhibit.
2. Response splitting / desync via keep-alive
- Backend sends response with wrong
Content-Length(off by N bytes). - Next response on same connection starts N bytes into the data.
- Pipelined client receives garbled responses for request 2+.
- Same root cause as http-request-smuggling but observed on the response side.
3. Pipelining response misalignment
- Older HTTP/1.1 spec allowed client to send
req1, req2, req3before any response. - Server responds in order. If server processes async out-of-order and returns wrong response → client receives wrong content.
- Most modern proxies/servers don’t pipeline; but if enabled, misalignment is a real bug.
4. Connection drop mid-response
- Backend writes headers then crashes.
- Proxy hands the half-written response to client OR retries on a new connection.
- Retry leaks: client-cached partial response + retry → user sees prior user’s content if cache hits.
5. HTTP/2 stream interleaving
- HTTP/2 multiplexes streams on one connection. Streams have IDs.
- HTTP/2 spec doesn’t strictly bind streams to authentication; auth is per-request (per-stream header).
- Bug: server caches “user” on connection on first stream → uses same on subsequent stream → wrong user.
- Variant of http2-h2-downgrade-desync-v3.
6. CONNECT method state
CONNECT proxy.example.com:443opens a tunnel through HTTP/2 connection.- After CONNECT, the same connection carries arbitrary TCP. Some HTTP/2 servers don’t properly transition state → header injection back to HTTP context.
7. WebSocket upgrade
- HTTP request becomes WebSocket via Upgrade. Same connection, different protocol.
- Server may inadvertently process post-upgrade frames as HTTP if upgrade-state machine has bug.
8. Keep-alive timeout race
- Server keeps connection N seconds. Client sends new request at N - 0.001s; server closes between read and respond.
- Race window: client resubmits on new connection; if request is non-idempotent (POST), duplicated.
9. Connection reuse across redirects
- Client receives 302 → reuses connection for the new URL.
- If new URL is on different security context (e.g., open redirect to attacker), some clients reuse the cookies/headers.
- Rare server-side bug; more client-side.
10. Proxy-server connection hijack
- TLS-terminating proxy holds backend connection. Bad cert validation in proxy → MitM proxies tamper with kept-alive flows.
11. Request smuggling persistence
- A smuggled request via http-request-smuggling persists in the next request’s slot on the kept-alive connection.
- Effect: subsequent requests from OTHER users on the same proxy-backend connection get the smuggled response.
- “Persistent” smuggling has been observed against major sites.
12. Response queue poisoning
- Browser pipelines requests over HTTP/1.1 keep-alive (rare in modern browsers but happens).
- Response queue mismatch → wrong response for wrong tab.
- Modern browsers fall back to one-request-per-connection to avoid this.
13. Slowloris connection exhaustion
- Many keep-alive connections from one attacker; never close.
- Server connection table fills; DoS.
- Mitigation:
keepalive_timeoutshort; per-IP connection cap.
Testing methodology
Black-box
- Burp Suite Repeater “Send connections in single connection” option.
- HTTP Request Smuggler extension.
- Custom scripts with persistent TCP socket + multiple requests.
Detection
- Send 10 sequential requests on one connection; tag each with a unique cookie/header.
- For each response, verify it matches the right request.
- Misalignment → response splitting bug.
Test scenario: response splitting
1
2
3
4
5
6
7
8
9
10
11
GET /a HTTP/1.1
Host: x
Connection: keep-alive
GET /b HTTP/1.1
Host: x
Connection: keep-alive
GET /c HTTP/1.1
Host: x
Connection: close
Inspect responses order + content. If /b response contains /a data → desync.
Source-side audit
1
2
3
4
5
6
7
8
9
# Custom connection / keep-alive handling
rg -n 'keep-?alive|setKeepAlive|KeepAliveTimeout' src/
# Connection-cached state
rg -n 'this\.connection\.\w+\s*=' src/
rg -n 'conn\.data|connection\.state' src/
# HTTP/2 stream state
rg -n 'http2\.Http2Stream|nghttp2' src/
# Manual buffer/framing
rg -n 'Content-Length|Transfer-Encoding' src/
Defence
Server config
keepalive_timeout60s typical for nginx; reduce to 15s if attack risk.- Per-IP connection cap.
- HTTP/2 stream limit per connection (default 100 in nginx).
- Strict Content-Length / Transfer-Encoding enforcement; reject ambiguous.
Application
- Authenticate per-request (header check), never per-connection.
- No connection-scoped mutable state.
- Logs include connection ID for debugging cross-request bleed.
Defence in depth
- HTTP/2 end-to-end (frontend + backend), avoid H2/H1 boundary.
- Use HTTP/3 where supported (QUIC’s framing is less ambiguous).