Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in Signin
DescriptionInappropriate implementation in Signin
ComponentSignin
Bug ClassLogic Error
Tracker505200733
Fix commita269ec32d5e9 (chromium/src) +39/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
TEST_F
components/signin/ios/browser/account_consistency_service_unittest.mm
modified

Files Changed

  • components/signin/ios/browser/account_consistency_service.mm
  • components/signin/ios/browser/account_consistency_service_unittest.mm
  • components/signin/public/base/signin_switches.cc
  • components/signin/public/base/signin_switches.h
From a269ec32d5e9601230e2d181d802bfa77a530fb8 Mon Sep 17 00:00:00 2001
From: Boris Sazonov <[email protected]>
Date: Fri, 24 Apr 2026 11:32:33 -0700
Subject: [PATCH] [Signin][iOS] Ignore X-ChromeManageAccounts header in subframes

Ignore X-ChromeManageAccounts if received in a non-main frame.
Gated by kIgnoreChromeManageAccountsInSubframes killswitch.

Bug: 505200733, 501892820
Change-Id: I1af663eb267611727fbdf299c3f9ca929479ffaa
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7786116
Commit-Queue: Boris Sazonov <[email protected]>
Reviewed-by: Jérôme Lebel <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1620357}
---

diff --git a/components/signin/ios/browser/account_consistency_service.mm b/components/signin/ios/browser/account_consistency_service.mm
index 93a5376..a0acff70 100644
--- a/components/signin/ios/browser/account_consistency_service.mm
+++ b/components/signin/ios/browser/account_consistency_service.mm
@@ -22,6 +22,7 @@
 #import "components/signin/core/browser/chrome_connected_header_helper.h"
 #import "components/signin/core/browser/signin_header_helper.h"
 #import "components/signin/ios/browser/features.h"
+#import "components/signin/public/base/signin_switches.h"
 #import "components/signin/public/identity_manager/accounts_cookie_mutator.h"
 #import "components/signin/public/identity_manager/accounts_in_cookie_jar_info.h"
 #import "google_apis/gaia/gaia_constants.h"
@@ -225,6 +226,13 @@
     return;
   }
 
+  if (!response_info.for_main_frame &&
+      base::FeatureList::IsEnabled(
+          switches::kIgnoreChromeManageAccountsInSubframes)) {
+    std::move(callback).Run(PolicyDecision::Allow());
+    return;
+  }
+
   GURL url = net::GURLWithNSURL(http_response.URL);
   // User is showing intent to navigate to a Google-owned domain. Set GAIA and
   // CHROME_CONNECTED cookies if the user is signed in (this is filtered in
diff --git a/components/signin/ios/browser/account_consistency_service_unittest.mm b/components/signin/ios/browser/account_consistency_service_unittest.mm
index 21dfbc3b..9df98b3 100644
--- a/components/signin/ios/browser/account_consistency_service_unittest.mm
+++ b/components/signin/ios/browser/account_consistency_service_unittest.mm
@@ -830,3 +830,27 @@
   EXPECT_EQ(1, delegate_.add_account_call_count_);
   EXPECT_EQ("[email protected]", delegate_.add_account_email_);
 }
+
+// Tests that the X-Chrome-Manage-Accounts header is ignored in a subframe.
+TEST_F(AccountConsistencyServiceTest, ChromeManageAccountsIgnoredInSubframe) {
+  base::test::ScopedFeatureList feature_list(
+      switches::kIgnoreChromeManageAccountsInSubframes);
+
+  NSDictionary* headers =
+      [NSDictionary dictionaryWithObject:@"action=DEFAULT"
+                                  forKey:@"X-Chrome-Manage-Accounts"];
+  NSHTTPURLResponse* response = [[NSHTTPURLResponse alloc]
+       initWithURL:[NSURL URLWithString:@"https://accounts.google.com/"]
+        statusCode:200
+       HTTPVersion:@"HTTP/1.1"
+      headerFields:headers];
+
+  SetWebStateHandler(&delegate_);
+
+  // When feature is enabled, header is ignored and response is allowed.
+  EXPECT_TRUE(web_state_.ShouldAllowResponse(response,
+                                             /* for_main_frame = */ false));
+  web_state_.SetCurrentURL(net::GURLWithNSURL(response.URL));
+  web_state_.OnPageLoaded(web::PageLoadCompletionStatus::SUCCESS);
+  EXPECT_EQ(0, delegate_.total_call_count());
+}
diff --git a/components/signin/public/base/signin_switches.cc b/components/signin/public/base/signin_switches.cc
index c0b4bbd..5efa90f 100644
--- a/components/signin/public/base/signin_switches.cc
+++ b/components/signin/public/base/signin_switches.cc
@@ -512,6 +512,9 @@
 #endif
 
 #if BUILDFLAG(IS_IOS)
+BASE_FEATURE(kIgnoreChromeManageAccountsInSubframes,
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 // Feature flag to ignore invalid grant errors in AuthenticationService.
 BASE_FEATURE(kIgnoreInvalidGrantError, base::FEATURE_DISABLED_BY_DEFAULT);
 #endif
diff --git a/components/signin/public/base/signin_switches.h b/components/signin/public/base/signin_switches.h
index 80aebb0..486418e 100644
--- a/components/signin/public/base/signin_switches.h
+++ b/components/signin/public/base/signin_switches.h
@@ -471,6 +471,10 @@
 #endif
 
 #if BUILDFLAG(IS_IOS)
+// Killswitch for ignoring X-Chrome-Manage-Accounts header in subframes.
+COMPONENT_EXPORT(SIGNIN_SWITCHES)
+BASE_DECLARE_FEATURE(kIgnoreChromeManageAccountsInSubframes);
+
 // Feature flag to ignore invalid grant errors in AuthenticationService.
 COMPONENT_EXPORT(SIGNIN_SWITCHES)
 BASE_DECLARE_FEATURE(kIgnoreInvalidGrantError);
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/components/signin/ios/browser/account_consistency_service_unittest.mm b/components/signin/ios/browser/account_consistency_service_unittest.mm
index 21dfbc3b..9df98b3 100644
--- a/components/signin/ios/browser/account_consistency_service_unittest.mm
+++ b/components/signin/ios/browser/account_consistency_service_unittest.mm
@@ -830,3 +830,27 @@
   EXPECT_EQ(1, delegate_.add_account_call_count_);
   EXPECT_EQ("[email protected]", delegate_.add_account_email_);
 }
+
+// Tests that the X-Chrome-Manage-Accounts header is ignored in a subframe.
+TEST_F(AccountConsistencyServiceTest, ChromeManageAccountsIgnoredInSubframe) {
+  base::test::ScopedFeatureList feature_list(
+      switches::kIgnoreChromeManageAccountsInSubframes);
+
+  NSDictionary* headers =
+      [NSDictionary dictionaryWithObject:@"action=DEFAULT"
+                                  forKey:@"X-Chrome-Manage-Accounts"];
+  NSHTTPURLResponse* response = [[NSHTTPURLResponse alloc]
+       initWithURL:[NSURL URLWithString:@"https://accounts.google.com/"]
+        statusCode:200
+       HTTPVersion:@"HTTP/1.1"
+      headerFields:headers];
+
+  SetWebStateHandler(&delegate_);
+
+  // When feature is enabled, header is ignored and response is allowed.
+  EXPECT_TRUE(web_state_.ShouldAllowResponse(response,
+                                             /* for_main_frame = */ false));
+  web_state_.SetCurrentURL(net::GURLWithNSURL(response.URL));
+  web_state_.OnPageLoaded(web::PageLoadCompletionStatus::SUCCESS);
+  EXPECT_EQ(0, delegate_.total_call_count());
+}
Loading diff…

Original Bug Report

reported by [email protected]

Potential Privilege Escalation via Unvalidated continue_url in Account Consistency Flow on iOS

Flapjack, 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. Please see go/chrome-ai-generated-security-bugs-faq for more information.

Overview: The X-Chrome-Manage-Accounts header parser on iOS does not validate the scheme of the continue_url parameter and processes the header even in subframes. This allows a malicious web page framing a GAIA endpoint to force the browser to open privileged chrome:// URLs in a new tab by inadvertently bypassing navigation security checks.

Affected files:

  • components/signin/ios/browser/account_consistency_service.mm
  • ios/chrome/browser/signin/model/account_consistency_browser_agent.mm
  • ios/chrome/browser/url_loading/model/scene_url_loading_service.mm

Estimated timestamp from git blame: 2025-04-30

Description

There is a potential privilege escalation vulnerability in Chrome for iOS where an untrusted web page can force the browser to navigate to restricted chrome:// WebUIs.

This vulnerability relies on a confused deputy attack involving the X-Chrome-Manage-Accounts HTTP response header, which is sent by Google GAIA endpoints (e.g., accounts.google.com) to coordinate account management flows with the browser.

When a response containing this header is received, the network traffic is intercepted by AccountConsistencyService::AccountConsistencyHandler::ShouldAllowResponse (components/signin/ios/browser/account_consistency_service.mm:217). This handler processes the header if the origin is a legitimate GAIA domain.

However, the iOS implementation has two critical weaknesses:

  1. Lack of Main Frame Check: Unlike the desktop implementation, the iOS handler fails to check response_info.for_main_frame. It processes the header even if the GAIA URL is loaded in a cross-origin <iframe>.
  2. Lack of Scheme Validation: The header is parsed into signin::ManageAccountsParams, and the continue_url parameter is extracted. The code builds a GURL from this string but fails to validate that its scheme is a standard web scheme (e.g., HTTP/HTTPS).

If the action is INCOGNITO, the untrusted continue_url is passed directly to AccountConsistencyBrowserAgent::OnGoIncognito (ios/chrome/browser/signin/model/account_consistency_browser_agent.mm:213), which creates an OpenNewTabCommand.

The command is routed to SceneUrlLoadingService::LoadUrlInNewTab (ios/chrome/browser/url_loading/model/scene_url_loading_service.mm:26). To facilitate opening the new tab, this service unconditionally sets the navigation transition type to ui::PAGE_TRANSITION_TYPED.

Finally, when the navigation begins, CRWWKNavigationHandler::shouldAllowAppSpecificURLNavigationAction evaluates whether to allow the navigation to the chrome:// URL. Because the transition type was artificially forged to PAGE_TRANSITION_TYPED (simulating user omnibox input), the security check is bypassed, and the privileged WebUI (e.g., chrome://flags or chrome://prefs-internals) is loaded.

Potential Exploit Steps

(Note: These are suggested steps based on code analysis; our tooling agent does not yet have the ability to run code.)

  1. An attacker creates a malicious webpage (evil.com).
  2. The malicious page embeds a hidden <iframe> pointing to a known GAIA endpoint that reflects URL parameters into the X-Chrome-Manage-Accounts header, such as: https://accounts.google.com/SignOutOptions?continue=chrome://flags.
  3. When the victim visits evil.com, the iframe loads. Although GAIA’s X-Frame-Options will prevent the content from rendering, Chrome’s ShouldAllowResponse intercepts the response headers beforehand.
  4. Chrome parses the header, observes the INCOGNITO action, and extracts chrome://flags as the continue_url.
  5. Chrome automatically navigates the current main tab back and opens a new incognito tab directly to chrome://flags, bypassing navigation restrictions.
  1. Main Frame Validation: Modify AccountConsistencyService::AccountConsistencyHandler::ShouldAllowResponse to check response_info.for_main_frame and ignore the X-Chrome-Manage-Accounts header if it arrives in a subframe, aligning with the desktop implementation.
  2. Scheme Validation: Validate the continue_url parameter to ensure it uses a safe scheme (http or https) before dispatching the action to the delegate.

Evaluated with Chrome root at commit: 4a3e9db74111a3c6c4b3acfd70050a05077cf27a


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.

View on issue tracker