Description
================================================================================
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:
1. lib/http2.c - HTTP/2 via nghttp2 (VULNERABLE)
2. lib/vquic/curl_ngtcp2.c - HTTP/3 via ngtcp2 (VULNERABLE)
3. lib/vquic/curl_osslq.c - HTTP/3 via OpenSSL QUIC (VULNERABLE)
================================================================================
TECHNICAL DETAILS
================================================================================
VULNERABLE CODE PATTERN:
1. 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"));
2. 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"));
3. 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
================================================================================
1. CLIENT connects to MALICIOUS SERVER using HTTP/2 or HTTP/3
2. 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"
3. CURL converts to HTTP/1-style headers WITHOUT validation:
Result: "X-Injected\r\nSet-Cookie: session=evil: foo\r\n"
4. 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:
1. Man-in-the-Middle with modified HTTP/2/3 responses
2. Compromised server sending malicious headers
3. 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:
1. lib/http2.c - Add is_valid_header() check before line 1705
2. lib/vquic/curl_ngtcp2.c - Add is_valid_h3_header() check before line 1220
3. 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.
## Impact
## Summary:
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:
1. lib/http2.c - HTTP/2 via nghttp2 (VULNERABLE)
2. lib/vquic/curl_ngtcp2.c - HTTP/3 via ngtcp2 (VULNERABLE)
3. lib/vquic/curl_osslq.c - HTTP/3 via OpenSSL QUIC (VULNERABLE)
================================================================================
TECHNICAL DETAILS
================================================================================
VULNERABLE CODE PATTERN:
1. 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"));
2. 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"));
3. 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
================================================================================
1. CLIENT connects to MALICIOUS SERVER using HTTP/2 or HTTP/3
2. 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"
3. CURL converts to HTTP/1-style headers WITHOUT validation:
Result: "X-Injected\r\nSet-Cookie: session=evil: foo\r\n"
4. 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:
1. Man-in-the-Middle with modified HTTP/2/3 responses
2. Compromised server sending malicious headers
3. 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:
1. lib/http2.c - Add is_valid_header() check before line 1705
2. lib/vquic/curl_ngtcp2.c - Add is_valid_h3_header() check before line 1220
3. 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.
## Impact
## Summary:
Basic Information
ID
H1:3481849
Published
Dec 29, 2025 at 23:21
Modified
Dec 30, 2025 at 09:56