CVE-2026-79065
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
forservices/network/public/cpp/header_util.cc |
modified |
Files Changed
services/network/public/cpp/header_util.ccservices/network/public/cpp/header_util_unittest.cc
Patch
From 4deb569d704e2efd8dedf6df8fa4778a7b77fe8a Mon Sep 17 00:00:00 2001 From: Keita Suzuki <[email protected]> Date: Mon, 06 Jul 2026 01:02:57 -0700 Subject: [PATCH] network: Restrict Connection request-header to known tokens IsRequestHeaderSafe() previously only rejected the Connection request header when its value was the exact string "Upgrade". Connection is a comma-separated token list (RFC 9110 7.6.1) where each token names a header that intermediaries drop before forwarding, and the value is passed through to the //net stack via MergeFrom(), so callers should not be able to list arbitrary header names there. Parse the value as a token list and only accept the connection-management options the network stack itself uses ("close" and "keep-alive"). This subsumes the old exact-match "Upgrade" check, so the now-unused kUnsafeHeaderValues table is removed and Connection is dropped from the outstanding TODO. TAG=agy CONV=13ec7a8f-4d73-4c01-b1b7-4d465e984eb3 Bug: 501799770 Change-Id: I3617e93de8f283450d26ce28ee073d4bf17c3e25 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8033741 Reviewed-by: Kenichi Ishibashi <[email protected]> Commit-Queue: Keita Suzuki <[email protected]> Cr-Commit-Position: refs/heads/main@{#1657017} --- diff --git a/services/network/public/cpp/header_util.cc b/services/network/public/cpp/header_util.cc index ad6a4d9a..db12b68 100644 --- a/services/network/public/cpp/header_util.cc +++ b/services/network/public/cpp/header_util.cc @@ -45,7 +45,8 @@ net::HttpRequestHeaders::kHost, // Trailers are not supported. - "Trailer", "Te", + "Trailer", + "Te", // Websockets use a different API. "Upgrade", @@ -63,19 +64,7 @@ "Set-Cookie", // TODO(mmenke): Figure out what to do about the remaining headers: - // Connection, Cookie, Date, Expect, Referer, Via. -}; - -// Headers that consumers are currently allowed to set, with the exception of -// certain values could cause problems. -// TODO(mmenke): Gather stats on these, and see if these headers can be banned -// outright instead. -const struct { - const char* name; - const char* value; -} kUnsafeHeaderValues[] = { - // Websockets use a different API. - {net::HttpRequestHeaders::kConnection, "Upgrade"}, + // Cookie, Date, Expect, Referer, Via. }; } // namespace @@ -86,10 +75,19 @@ return false; } - for (const auto& header : kUnsafeHeaderValues) { - if (base::EqualsCaseInsensitiveASCII(header.name, key) && - base::EqualsCaseInsensitiveASCII(header.value, value)) { - return false; + // The Connection header is a comma-separated list of tokens. Per RFC 9110 + // section 7.6.1, intermediaries treat each listed token as the name of a + // header to remove before forwarding, so only allow the connection-management + // options that the network stack itself uses. Websockets use a different API, + // so "upgrade" is not needed here. + if (base::EqualsCaseInsensitiveASCII(key, + net::HttpRequestHeaders::kConnection)) { + net::HttpUtil::ValuesIterator tokens(value, ','); + while (tokens.GetNext()) { + if (!base::EqualsCaseInsensitiveASCII(tokens.value(), "close") && + !base::EqualsCaseInsensitiveASCII(tokens.value(), "keep-alive")) { + return false; + } } } diff --git a/services/network/public/cpp/header_util_unittest.cc b/services/network/public/cpp/header_util_unittest.cc index b3e43057..95c84172 100644 --- a/services/network/public/cpp/header_util_unittest.cc +++ b/services/network/public/cpp/header_util_unittest.cc @@ -30,9 +30,25 @@ {net::HttpRequestHeaders::kConnection, "Upgrade", false}, {net::HttpRequestHeaders::kConnection, "Close", true}, + {net::HttpRequestHeaders::kConnection, "keep-alive", true}, + {net::HttpRequestHeaders::kConnection, "keep-alive, close", true}, + {net::HttpRequestHeaders::kConnection, "keep-alive, Upgrade", false}, + {net::HttpRequestHeaders::kConnection, "X-Forwarded-For", false}, + {net::HttpRequestHeaders::kConnection, "close, X-Real-IP", false}, + {net::HttpRequestHeaders::kConnection, "Authorization, keep-alive", + false}, + {net::HttpRequestHeaders::kConnection, "", true}, + {net::HttpRequestHeaders::kConnection, " ", true}, + {net::HttpRequestHeaders::kConnection, ",", true}, + {net::HttpRequestHeaders::kConnection, ",,,", true}, + {net::HttpRequestHeaders::kConnection, " , , ", true}, + {net::HttpRequestHeaders::kConnection, "close, ", true}, + {net::HttpRequestHeaders::kConnection, ", keep-alive", true}, + {net::HttpRequestHeaders::kConnection, ", keep-alive, , close, ", true}, {net::HttpRequestHeaders::kTransferEncoding, "Chunked", false}, {net::HttpRequestHeaders::kTransferEncoding, "Chunky", false}, {"cOnNeCtIoN", "uPgRaDe", false}, + {"cOnNeCtIoN", "kEeP-aLiVe", true}, {net::HttpRequestHeaders::kProxyAuthorization, "Basic Zm9vOmJhcg==", false}, @@ -78,6 +94,18 @@ {net::HttpRequestHeaders::kConnection, "Upgrade", false}, {net::HttpRequestHeaders::kConnection, "Close", true}, + {net::HttpRequestHeaders::kConnection, "keep-alive", true}, + {net::HttpRequestHeaders::kConnection, "keep-alive, Upgrade", false}, + {net::HttpRequestHeaders::kConnection, "X-Forwarded-For", false}, + {net::HttpRequestHeaders::kConnection, "close, X-Real-IP", false}, + {net::HttpRequestHeaders::kConnection, "", true}, + {net::HttpRequestHeaders::kConnection, " ", true}, + {net::HttpRequestHeaders::kConnection, ",", true}, + {net::HttpRequestHeaders::kConnection, ",,,", true}, + {net::HttpRequestHeaders::kConnection, " , , ", true}, + {net::HttpRequestHeaders::kConnection, "close, ", true}, + {net::HttpRequestHeaders::kConnection, ", keep-alive", true}, + {net::HttpRequestHeaders::kConnection, ", keep-alive, , close, ", true}, {net::HttpRequestHeaders::kTransferEncoding, "Chunked", false}, {net::HttpRequestHeaders::kTransferEncoding, "Chunky", false}, {"cOnNeCtIoN", "uPgRaDe", false},
Regression Test / PoC
diff --git a/services/network/public/cpp/header_util_unittest.cc b/services/network/public/cpp/header_util_unittest.cc
index b3e43057..95c84172 100644
--- a/services/network/public/cpp/header_util_unittest.cc
+++ b/services/network/public/cpp/header_util_unittest.cc
@@ -30,9 +30,25 @@
{net::HttpRequestHeaders::kConnection, "Upgrade", false},
{net::HttpRequestHeaders::kConnection, "Close", true},
+ {net::HttpRequestHeaders::kConnection, "keep-alive", true},
+ {net::HttpRequestHeaders::kConnection, "keep-alive, close", true},
+ {net::HttpRequestHeaders::kConnection, "keep-alive, Upgrade", false},
+ {net::HttpRequestHeaders::kConnection, "X-Forwarded-For", false},
+ {net::HttpRequestHeaders::kConnection, "close, X-Real-IP", false},
+ {net::HttpRequestHeaders::kConnection, "Authorization, keep-alive",
+ false},
+ {net::HttpRequestHeaders::kConnection, "", true},
+ {net::HttpRequestHeaders::kConnection, " ", true},
+ {net::HttpRequestHeaders::kConnection, ",", true},
+ {net::HttpRequestHeaders::kConnection, ",,,", true},
+ {net::HttpRequestHeaders::kConnection, " , , ", true},
+ {net::HttpRequestHeaders::kConnection, "close, ", true},
+ {net::HttpRequestHeaders::kConnection, ", keep-alive", true},
+ {net::HttpRequestHeaders::kConnection, ", keep-alive, , close, ", true},
{net::HttpRequestHeaders::kTransferEncoding, "Chunked", false},
{net::HttpRequestHeaders::kTransferEncoding, "Chunky", false},
{"cOnNeCtIoN", "uPgRaDe", false},
+ {"cOnNeCtIoN", "kEeP-aLiVe", true},
{net::HttpRequestHeaders::kProxyAuthorization,
"Basic Zm9vOmJhcg==", false},
@@ -78,6 +94,18 @@
{net::HttpRequestHeaders::kConnection, "Upgrade", false},
{net::HttpRequestHeaders::kConnection, "Close", true},
+ {net::HttpRequestHeaders::kConnection, "keep-alive", true},
+ {net::HttpRequestHeaders::kConnection, "keep-alive, Upgrade", false},
+ {net::HttpRequestHeaders::kConnection, "X-Forwarded-For", false},
+ {net::HttpRequestHeaders::kConnection, "close, X-Real-IP", false},
+ {net::HttpRequestHeaders::kConnection, "", true},
+ {net::HttpRequestHeaders::kConnection, " ", true},
+ {net::HttpRequestHeaders::kConnection, ",", true},
+ {net::HttpRequestHeaders::kConnection, ",,,", true},
+ {net::HttpRequestHeaders::kConnection, " , , ", true},
+ {net::HttpRequestHeaders::kConnection, "close, ", true},
+ {net::HttpRequestHeaders::kConnection, ", keep-alive", true},
+ {net::HttpRequestHeaders::kConnection, ", keep-alive, , close, ", true},
{net::HttpRequestHeaders::kTransferEncoding, "Chunked", false},
{net::HttpRequestHeaders::kTransferEncoding, "Chunky", false},
{"cOnNeCtIoN", "uPgRaDe", false},
Original Bug Report
Renderer can control Connection header, enabling hop-by-hop header stripping in HTTP/1.1
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports without the Chrome Security team.
Overview: A compromised renderer can specify arbitrary values in the Connection header because the Network Service’s safety check only blocks the value Upgrade. This allows an attacker to exploit hop-by-hop header stripping on HTTP/1.1 intermediaries, potentially bypassing security controls like IP allowlists on credentialed cross-origin requests.
Affected files:
services/network/public/cpp/header_util.ccnet/http/http_network_transaction.ccnet/http/http_stream_parser.ccservices/network/cors/cors_url_loader_factory.ccservices/network/cors/cors_url_loader.ccservices/network/url_loader.ccservices/network/url_loader_util.ccservices/network/public/cpp/http_request_headers_mojom_traits.cc
Estimated timestamp from git blame: 2019-10-08
Summary
The Network Service uses network::AreRequestHeadersSafe (services/network/public/cpp/header_util.cc) to validate HTTP headers sent by a renderer process. However, the Connection header is not present in the kUnsafeHeaders list. Instead, it is only checked in kUnsafeHeaderValues for the exact, case-insensitive string “Upgrade”.
Because any other value is permitted, a compromised renderer can send a custom Connection header (e.g., Connection: X-Forwarded-For). According to RFC 7230 §6.1, compliant intermediaries must treat any header listed in the Connection header as a hop-by-hop header and strip it before forwarding the request. This allows an attacker to forcefully strip security-critical headers (like X-Forwarded-For, X-Real-IP, or internal authentication tokens) on requests targeting victim sites over HTTP/1.1.
Technical Details
- Validation Gap: In
services/network/public/cpp/header_util.cc,IsRequestHeaderSafeallows theConnectionheader as long as the value is not “Upgrade”. - CORS Preflight Bypass:
Connectionis defined as a forbidden header bynet::HttpUtil::IsSafeHeader. Consequently,CorsUnsafeNotForbiddenRequestHeaderNames(services/network/cors/cors_util.cc) ignores it, meaning the customConnectionheader does not trigger a CORS preflight. - Header Overwriting: In
net::HttpNetworkTransaction::BuildRequestHeaders(net/http/http_network_transaction.cc), a defaultConnection: keep-aliveis set. Later in the same method,request_headers_.MergeFrom(request_->extra_headers)is called. This finds the existingConnectionheader and overwrites it with the attacker’s value. - Verbatim Serialization: For HTTP/1.1,
net::HttpStreamParser::SendRequestserializes the headers verbatim, sending the attacker-controlledConnectionheader over the wire.
Potential Reproduction Steps
Note: These are potential steps based on code analysis; a working proof-of-concept has not been executed.
- Gain arbitrary code execution in a renderer process.
- Obtain a valid
network::mojom::URLLoaderFactory(e.g., the frame’s subresource factory). - Construct a
network::ResourceRequesttargetinghttps://victim.example/(reached over HTTP/1.1). - Set
credentials_modetokIncludeto attach the user’s cookies for the victim site. - Inject the custom header into
request.headers, e.g.,{"Connection": "X-Forwarded-For"}. - Invoke
factory->CreateLoaderAndStart()via Mojo. - The Network Service merges the header, overwriting the default
keep-alive, and sends it to the victim site without a CORS preflight. - An RFC-compliant intermediary in front of the victim site processes the request, sees
Connection: X-Forwarded-For, and strips theX-Forwarded-Forheader before forwarding it to the backend.
Suggested Fix
The Connection header should be completely blocked from being set by untrusted callers, aligning with the Fetch specification’s forbidden header names. Move net::HttpRequestHeaders::kConnection from kUnsafeHeaderValues to the kUnsafeHeaders list in services/network/public/cpp/header_util.cc.
Evaluated with Chrome root at commit: 096fc8fdbfacf2546485756d03f160a3d04fcc9b
Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.