CVE-2026-34441: cpp-httplib HTTP Request Smuggling via Unconsumed GET Request Body
CVE-2026-34441 is verified against yhirose/cpp-httplib · github affected versions: <=0.38.0 per GHSA metadata fixed version: 0.40.0 vulnerability class: Request Smuggling This high reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00127.
pruva-verify REPRO-2026-00127 curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00127/artifacts/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh cpp-httplib (16.3k+ GitHub stars) is vulnerable to HTTP Request Smuggling in versions ≤0.38.0. The server's static file handler serves GET responses WITHOUT consuming the request body, leaving body bytes on the TCP stream to be interpreted as a new HTTP request on keep-alive connections.
# Root Cause Analysis Report - CVE-2026-34441
## Summary
cpp-httplib versions ≤0.38.0 are vulnerable to HTTP Request Smuggling. The vulnerability occurs because the server's static file handler serves GET responses WITHOUT consuming the request body. When a GET request with a Content-Length header matches a static file mount point, the `handle_file_request()` function returns true and causes `routing()` to return early, bypassing the `expect_content()` check that would normally read the body. This leaves request body bytes on the TCP stream, which are then interpreted as a new HTTP request on keep-alive connections.
## Impact
**Package:** cpp-httplib (header-only C++ HTTP library)
**Affected Versions:** ≤0.38.0
**Fixed Version:** 0.40.0
**Risk Level:** HIGH
**Consequences:**
- HTTP Request Smuggling: Arbitrary HTTP requests on keep-alive connections
- Access Control Bypass: Smuggled requests bypass proxy-level authentication
- Cache Poisoning: Smuggled responses cached for wrong URLs
- Request Hijacking: Behind reverse proxy, smuggled request paired with other user's request
## Root Cause
### Technical Explanation
The vulnerability exists in the `Server::routing()` function in `httplib.h`:
```cpp
// File handler
if ((req.method == "GET" || req.method == "HEAD") &&
handle_file_request(req, res)) {
return true; // Returns early for static files
}
if (detail::expect_content(req)) { // This is never reached for GET static files
// Content reader handler - would read the body
...
}
```
**The Bug Flow:**
1. Client sends GET request with Content-Length header and a body containing a smuggled HTTP request
2. Server matches request to static file mount point via `handle_file_request()`
3. `routing()` returns true immediately (early return)
4. The `expect_content()` check is never executed for static file requests
5. Request body bytes remain unread in the TCP stream
6. Server sends response for first request and keeps connection alive
7. Next iteration of keep-alive loop reads body bytes as a new HTTP request
8. Server processes the smuggled request
**Code Location:** Server::routing() at approximately line 11542-11544 in v0.38.0
## Reproduction Evidence
Our differential testing confirms the vulnerability:
**v0.38.0 (Vulnerable):**
- 2 HTTP responses received from single payload
- SECRET content leaked from smuggled request
- Server processed 2 requests: /index.html and /secret.html
- Total bytes received: 360 (two full HTTP responses)
**v0.40.0 (Fixed):**
- 1 HTTP response received
- No SECRET content leaked
- Server processed 1 request: /index.html only
- Total bytes received: 179 (single HTTP response)
**Exploit Payload:**
```
GET /index.html HTTP/1.1\r\n
Host: localhost\r\n
Content-Length: 38\r\n
Connection: keep-alive\r\n
\r\n
GET /secret.html HTTP/1.1\r\n
Host: x\r\n
\r\n
```
The first request (GET /index.html) has a Content-Length: 38 header pointing to the second request in the body. The vulnerable server serves /index.html without consuming the body, then the body bytes are read as a separate HTTP request on the keep-alive connection.
## Fix Analysis
Version 0.40.0 properly handles the request body in one of these ways:
1. The body is consumed before the connection is returned to the keep-alive pool
2. The connection is closed after requests with unconsumed bodies
3. The expect_content() check is moved earlier in the routing flow
The fix ensures that body bytes cannot be left in the stream to be misinterpreted as a new request.
## Attack Scenario
1. Attacker sends crafted request to a reverse proxy with a smuggled request in the body
2. Proxy forwards to cpp-httplib backend (v0.38.0)
3. Backend serves the legitimate request but doesn't consume the body
4. Backend keeps connection alive to proxy
5. Backend reads body as second request and processes it
6. Second response is associated with the proxy's original request
7. Attacker receives response for smuggled request, potentially accessing protected resources
## Recommendations
1. **Upgrade** to cpp-httplib v0.40.0 or later
2. **Validate Content-Length** headers on GET requests at the proxy/load balancer level
3. **Disable keep-alive** for requests with bodies if using affected versions
4. **Monitor** for anomalous request patterns in server logs
## References
- CVE-2026-34441
- cpp-httplib repository: https://github.com/yhirose/cpp-httplib
- Affected version: v0.38.0
- Fixed version: v0.40.0
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired. Phases: support triages the advisory · repro reproduces it · vuln_variant confirms the fix blocks it · judge verifies.
Loading session...
Scripts, logs, diffs, and output captured during the reproduction.
No artifacts available