curl · Report #3481849
HTTP/2 and HTTP/3 Header Injection in curl
How it was found
================================================================================ VULNERABILITY REPORT: HTTP/2 and HTTP/3 Header Injection in curl
VULNERABILITY TYPE: Response Header Injection / HTTP Response Splitting SEVERITY: HIGH (CVSS 7.5+) AFFECTED VERSIONS: curl latest (as of 2025-12-29) STATUS: 0-DAY (UNPATCHED in http2.c, curl_ngtcp2.c, curl_osslq.c)
================================================================================ EXECUTIVE SUMMARY
A critical header injection vulnerability exists in curl's HTTP/2 and HTTP/3 protocol handlers. When receiving HTTP/2 or HTTP/3 headers from a malicious server, curl directly incorporates header names and values into HTTP/1-style header strings WITHOUT validating for CR (\r), LF (\n), or null (\0) bytes.
This vulnerability was PARTIALLY FIXED on 2025-12-27 in commit 6842d4e for the QUICHE HTTP/3 backend ONLY. Three other backends remain vulnerable:
- lib/http2.c - HTTP/2 via nghttp2 (VULNERABLE)
- lib/vquic/curl_ngtcp2.c - HTTP/3 via ngtcp2 (VULNERABLE)
- lib/vquic/curl_osslq.c - HTTP/3 via OpenSSL QUIC (VULNERABLE)
================================================================================ TECHNICAL DETAILS
VULNERABLE CODE PATTERN:
-
HTTP/2 (lib/http2.c, lines 1705-1717):
/* convert to an HTTP1-style header */ curlx_dyn_reset(&ctx->scratch); result = curlx_dyn_addn(&ctx->scratch, (const char *)name, namelen); // UNVALIDATED if(!result) result = curlx_dyn_addn(&ctx->scratch, STRCONST(": ")); if(!result) result = curlx_dyn_addn(&ctx->scratch, (const char *)value, valuelen); // UNVALIDATED if(!result) result = curlx_dyn_addn(&ctx->scratch, STRCONST("\r\n"));
-
HTTP/3 via ngtcp2 (lib/vquic/curl_ngtcp2.c, lines 1220-1229):
curlx_dyn_reset(&ctx->scratch); result = curlx_dyn_addn(&ctx->scratch, (const char *)h3name.base, h3name.len); // UNVALIDATED if(!result) result = curlx_dyn_addn(&ctx->scratch, STRCONST(": ")); if(!result) result = curlx_dyn_addn(&ctx->scratch, (const char *)h3val.base, h3val.len); // UNVALIDATED if(!result) result = curlx_dyn_addn(&ctx->scratch, STRCONST("\r\n"));
-
HTTP/3 via OpenSSL QUIC (lib/vquic/curl_osslq.c, lines 873-887):
result = write_resp_raw(cf, data, h3name.base, h3name.len, FALSE); // UNVALIDATED // ... result = write_resp_raw(cf, data, h3val.base, h3val.len, FALSE); // UNVALIDATED
PATCHED CODE (curl_quiche.c, commit 6842d4e):
static bool is_valid_h3_header(const uint8_t *hdr, size_t hlen) { while(hlen--) { switch(*hdr++) { case '\n': case '\r': case '\0': return FALSE; } } return TRUE; }
// Now validates before using: if(is_valid_h3_header(value, value_len) && is_valid_h3_header(name, name_len)) { // ... process header }
================================================================================ ATTACK SCENARIO
-
CLIENT connects to MALICIOUS SERVER using HTTP/2 or HTTP/3
-
MALICIOUS SERVER sends an HTTP/2 or HTTP/3 response with crafted headers:
Header Name: "X-Injected\r\nSet-Cookie: session=evil" Header Value: "foo"
OR
Header Name: "X-Normal" Header Value: "foo\r\nX-Injected-Header: malicious-value"
-
CURL converts to HTTP/1-style headers WITHOUT validation:
Result: "X-Injected\r\nSet-Cookie: session=evil: foo\r\n"
-
APPLICATION processing these headers may:
- Accept injected cookies (cookie injection)
- Process injected headers as legitimate
- Experience cache poisoning if used as a proxy/CDN
- Have security headers bypassed
================================================================================ EXPLOITATION CONDITIONS
REQUIREMENTS:
- Victim must connect to attacker-controlled server using HTTP/2 or HTTP/3
- Application must process curl's response headers
- Attacker controls the server-side HTTP/2 or HTTP/3 implementation
ATTACK VECTORS:
- Man-in-the-Middle with modified HTTP/2/3 responses
- Compromised server sending malicious headers
- Malicious redirect to attacker's HTTP/2/3 server
================================================================================ IMPACT ASSESSMENT
- Cookie Injection: Attacker can set arbitrary cookies
- Cache Poisoning: Stored responses may contain injected headers
- Security Header Bypass: CSP, HSTS, etc. can be manipulated
- Response Splitting: Classic HTTP response splitting attacks
- Information Disclosure: Via crafted headers in error messages
Note: HTTP/2 has ADDITIONAL risk factor: - Line 460: nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation(o, 1); - This DISABLES RFC 9113 header validation in nghttp2!
================================================================================ RECOMMENDED FIX
Apply the same validation added to curl_quiche.c (commit 6842d4e) to:
- lib/http2.c - Add is_valid_header() check before line 1705
- lib/vquic/curl_ngtcp2.c - Add is_valid_h3_header() check before line 1220
- lib/vquic/curl_osslq.c - Add is_valid_h3_header() check before line 873
SUGGESTED PATCH:
// Add to each file: static bool is_valid_header(const uint8_t *hdr, size_t hlen) { while(hlen--) { switch(*hdr++) { case '\n': case '\r': case '\0': return FALSE; } } return TRUE; }
// Then validate before processing: if(!is_valid_header(name, namelen) || !is_valid_header(value, valuelen)) { // Return error or skip header return NGHTTP2_ERR_CALLBACK_FAILURE; // For HTTP/2 }
================================================================================ CVE ASSIGNMENT
This vulnerability should be assigned a CVE identifier.
Related: Commit 6842d4e (partial fix for QUICHE only) mentions issue #20101
================================================================================ TIMELINE
2025-12-27: Partial fix committed for QUICHE backend (commit 6842d4e) 2025-12-29: Discovery of same vulnerability in HTTP/2 and other HTTP/3 backends 2025-12-29: This report generated
================================================================================ RESEARCHER
Identified via static code analysis of curl source repository.