# REPRO-2026-00127: cpp-httplib HTTP Request Smuggling via Unconsumed GET Request Body ## Summary Status: published Severity: high Type: security Confidence: Unknown ## Identifiers REPRO ID: REPRO-2026-00127 CVE: CVE-2026-34441 ## Package Name: yhirose/cpp-httplib Ecosystem: github Affected: <=0.38.0 per GHSA metadata Fixed: 0.40.0 ## Root Cause # 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 ## Reproduction Details Reproduced: 2026-04-04T09:53:53.920Z Duration: 3689 seconds Tool calls: 300 Turns: Unknown Handoffs: 3 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00127 pruva-verify CVE-2026-34441 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00127&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00127/artifacts/repro/reproduction_steps.sh chmod +x reproduction_steps.sh ./reproduction_steps.sh WARNING: Run in a sandboxed environment. This exploits a real vulnerability. ## References - NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-34441 - Source: https://github.com/advisories/GHSA-jv63-rm9j-6jwc ## Artifacts - repro/rca_report.md (analysis, 4468 bytes) - repro/reproduction_steps.sh (reproduction_script, 9973 bytes) - vuln_variant/rca_report.md (analysis, 8265 bytes) - vuln_variant/reproduction_steps.sh (reproduction_script, 16994 bytes) - bundle/ticket.json (other, 2790 bytes) - bundle/AGENTS.repro.md (documentation, 855 bytes) - bundle/ticket.md (ticket, 2343 bytes) - repro/artifacts/python_request.raw (other, 159 bytes) - repro/artifacts/exploit_result.json (other, 418 bytes) - repro/artifacts/smuggle_response.raw (other, 184 bytes) - repro/artifacts/server_v0.40.0.log (log, 22 bytes) - repro/artifacts/build_vuln.log (log, 0 bytes) - repro/artifacts/test_fixed (other, 569856 bytes) - repro/artifacts/build_fixed.log (log, 0 bytes) - repro/artifacts/library_test.cpp (other, 5582 bytes) - repro/artifacts/test_vuln (other, 567616 bytes) - repro/artifacts/server_v0.38.0.log (log, 45 bytes) - repro/artifacts/response_v0.38.0.bin (other, 360 bytes) - repro/artifacts/final_results.json (other, 257 bytes) - repro/artifacts/response_v0.40.0.bin (other, 179 bytes) - repro/artifacts/request_hex.txt (other, 823 bytes) - repro/artifacts/payload_v0.38.0.bin (other, 127 bytes) - repro/artifacts/result_v0.40.0.json (other, 167 bytes) - repro/artifacts/response.raw (other, 184 bytes) - repro/artifacts/output_vuln.txt (other, 634 bytes) - repro/artifacts/result_v0.38.0.json (other, 186 bytes) - repro/artifacts/server.log (log, 35 bytes) - repro/artifacts/output_fixed.txt (other, 634 bytes) - repro/artifacts/smuggle_request.raw (other, 167 bytes) - repro/artifacts/payload_v0.40.0.bin (other, 127 bytes) - repro/artifacts/results.json (other, 262 bytes) - repro/artifacts/request.raw (other, 155 bytes) - repro/runtime_manifest.json (other, 851 bytes) - repro/validation_verdict.json (other, 756 bytes) - vuln_variant/variant_manifest.json (other, 2089 bytes) - vuln_variant/source_identity.json (other, 1009 bytes) - vuln_variant/patch_analysis.md (documentation, 6484 bytes) - vuln_variant/validation_verdict.json (other, 2130 bytes) - logs/vuln_variant/server_v0.38.0_v5_http10.log (log, 26 bytes) - logs/vuln_variant/result_v0.40.0_v3_head.json (other, 120 bytes) - logs/vuln_variant/payload_v0.38.0_v4_oversized.log (log, 51 bytes) - logs/vuln_variant/test_run.log (log, 2794 bytes) - logs/vuln_variant/server_v0.40.0_v3_head.log (log, 27 bytes) - logs/vuln_variant/server_v0.38.0_v4_oversized.log (log, 26 bytes) - logs/vuln_variant/result_v0.38.0_v5_http10.json (other, 122 bytes) - logs/vuln_variant/result_v0.40.0_v4_oversized.json (other, 125 bytes) - logs/vuln_variant/result_v0.40.0_v2_cl_te.json (other, 121 bytes) - logs/vuln_variant/payload_v0.38.0_v2_cl_te.log (log, 236 bytes) - logs/vuln_variant/result_v0.38.0_v3_head.json (other, 120 bytes) - logs/vuln_variant/result_v0.38.0_v2_cl_te.json (other, 121 bytes) - logs/vuln_variant/payload_v0.38.0_v1_get_baseline.log (log, 74 bytes) - logs/vuln_variant/payload_v0.38.0_v5_http10.log (log, 51 bytes) - logs/vuln_variant/payload_v0.40.0_v2_cl_te.log (log, 155 bytes) - logs/vuln_variant/payload_v0.40.0_v1_get_baseline.log (log, 51 bytes) - logs/vuln_variant/payload_v0.40.0_v5_http10.log (log, 51 bytes) - logs/vuln_variant/final_variant_results.json (other, 586 bytes) - logs/vuln_variant/result_v0.38.0_v4_oversized.json (other, 125 bytes) - logs/vuln_variant/result_v0.38.0_v1_get_baseline.json (other, 128 bytes) - logs/vuln_variant/server_v0.38.0_v1_get_baseline.log (log, 88 bytes) - logs/vuln_variant/payload_v0.40.0_v3_head.log (log, 51 bytes) - logs/vuln_variant/server_v0.40.0_v4_oversized.log (log, 26 bytes) - logs/vuln_variant/server_v0.40.0_v1_get_baseline.log (log, 26 bytes) - logs/vuln_variant/build_v0.38.0.log (log, 0 bytes) - logs/vuln_variant/server_v0.40.0_v5_http10.log (log, 26 bytes) - logs/vuln_variant/server_v0.40.0_v2_cl_te.log (log, 26 bytes) - logs/vuln_variant/server_v0.38.0_v2_cl_te.log (log, 26 bytes) - logs/vuln_variant/result_v0.40.0_v5_http10.json (other, 122 bytes) - logs/vuln_variant/result_v0.40.0_v1_get_baseline.json (other, 128 bytes) - logs/vuln_variant/payload_v0.38.0_v3_head.log (log, 74 bytes) - logs/vuln_variant/payload_v0.40.0_v4_oversized.log (log, 51 bytes) - logs/vuln_variant/server_v0.38.0_v3_head.log (log, 89 bytes) - logs/vuln_variant/build_v0.40.0.log (log, 0 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00127 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00127/artifacts/repro/reproduction_steps.sh - Web: https://pruva.dev/r/REPRO-2026-00127 ## For AI Assistants To help users with this reproduction: 1. The `pruva-verify` command is the simplest way to run the reproduction 2. The script downloads and runs automatically in an isolated temp directory 3. Always recommend running in a VM, container, or disposable environment 4. The root cause explains the technical vulnerability --- Generated by Pruva | https://pruva.dev