CVE-2026-13913
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_injection_handler.mm |
modified |
Files Changed
components/autofill/ios/browser/autofill_util.mmios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_injection_handler.mmios/chrome/browser/autofill/ui_bundled/manual_fill/password_view_controller_egtest.mm
Patch
From bd6ad46ebdd81517dfc6c15f8492511a6ff568f4 Mon Sep 17 00:00:00 2001 From: Leo Zhao <[email protected]> Date: Wed, 20 May 2026 13:58:26 -0700 Subject: [PATCH] [iOS] Eliminate potential security risks for "Autofill form" button When "AUtofill form" was tapped, it did not check whether the connection was HTTPS. This CL adds a check before autofilling credentials to ensure the filling is done on a form that is secure. This is part 1 of the Suggested Fix from the reported issue. The issue also suggests a fix to "Enforce Re-authentication". After reviewing the process, we believe the conditions under which it can be exploited are very unlikely to be met, and therefore do not justify adding an extra layer of inconvenience to a common use case. Bug: 508260619 Change-Id: I19df5a4811772c316269c9fd6e81a58b7e610a1f Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7810331 Commit-Queue: Leo Zhao <[email protected]> Reviewed-by: Tommy Martino <[email protected]> Cr-Commit-Position: refs/heads/main@{#1633817} --- diff --git a/components/autofill/ios/browser/autofill_util.mm b/components/autofill/ios/browser/autofill_util.mm index 8476e67..3588dca 100644 --- a/components/autofill/ios/browser/autofill_util.mm +++ b/components/autofill/ios/browser/autofill_util.mm @@ -83,8 +83,13 @@ return false; } + const GURL& url = nav_item->GetURL(); + if (net::IsLocalhost(url)) { + return true; + } + const web::SSLStatus& ssl = nav_item->GetSSL(); - return nav_item->GetURL().SchemeIsCryptographic() && ssl.certificate && + return url.SchemeIsCryptographic() && ssl.certificate && !net::IsCertStatusError(ssl.cert_status); } diff --git a/ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_injection_handler.mm b/ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_injection_handler.mm index e4b14d4..97e774e 100644 --- a/ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_injection_handler.mm +++ b/ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_injection_handler.mm @@ -197,6 +197,10 @@ - (void)autofillFormWithCredential:(ManualFillCredential*)credential shouldReauth:(BOOL)shouldReauth { + if (![self canUserInjectInPasswordField:NO requiresHTTPS:YES]) { + return; + } + if (shouldReauth && [self.reauthenticationModule canAttemptReauth]) { NSString* reason = l10n_util::GetNSString(IDS_IOS_AUTOFILL_REAUTH_REASON); __weak __typeof(self) weakSelf = self; diff --git a/ios/chrome/browser/autofill/ui_bundled/manual_fill/password_view_controller_egtest.mm b/ios/chrome/browser/autofill/ui_bundled/manual_fill/password_view_controller_egtest.mm index 1dd624be..9d1de37 100644 --- a/ios/chrome/browser/autofill/ui_bundled/manual_fill/password_view_controller_egtest.mm +++ b/ios/chrome/browser/autofill/ui_bundled/manual_fill/password_view_controller_egtest.mm @@ -4,6 +4,7 @@ #import "base/i18n/message_formatter.h" #import "base/ios/ios_util.h" +#import "base/path_service.h" #import "base/strings/sys_string_conversions.h" #import "base/strings/utf_string_conversions.h" #import "base/test/ios/wait_util.h" @@ -34,6 +35,7 @@ #import "ios/chrome/test/earl_grey/chrome_test_case.h" #import "ios/testing/earl_grey/earl_grey_test.h" #import "ios/web/public/test/element_selector.h" +#import "net/test/embedded_test_server/default_handlers.h" #import "net/test/embedded_test_server/embedded_test_server.h" #import "ui/base/l10n/l10n_util.h" #import "ui/base/l10n/l10n_util_mac.h" @@ -254,7 +256,9 @@ } // namespace // Integration Tests for Mannual Fallback Passwords View Controller. -@interface PasswordViewControllerTestCase : ChromeTestCase +@interface PasswordViewControllerTestCase : ChromeTestCase { + std::unique_ptr<net::test_server::EmbeddedTestServer> _HTTPSServer; +} // URL of the current page. @property(assign) GURL URL; @@ -265,6 +269,7 @@ - (void)setUp { [super setUp]; + _HTTPSServer = nil; GREYAssertTrue(self.testServer->Start(), @"Test server failed to start."); self.URL = self.testServer->GetURL(kFormHTMLFile); [self loadLoginPage]; @@ -293,6 +298,7 @@ [MetricsAppInterface releaseUserActionTester]); chrome_test_util::GREYAssertErrorNil( [MetricsAppInterface releaseHistogramTester]); + _HTTPSServer = nil; [super tearDownHelper]; } @@ -320,6 +326,34 @@ [ChromeEarlGrey waitForWebStateContainingText:"hello!"]; } +// Loads the page over HTTPS and proceeds past the SSL warning interstitial if +// present. +- (void)loadHTTPSLoginPage { + [ChromeEarlGrey loadURL:self.URL]; + + // Check if the SSL warning page is displayed by verifying if the + // "details-button" element exists on the page. + base::Value result = [ChromeEarlGrey + evaluateJavaScript:@"document.getElementById('details-button') !== null"]; + if (result.is_bool() && result.GetBool()) { + [ChromeEarlGrey tapWebStateElementWithID:@"details-button"]; + [ChromeEarlGrey tapWebStateElementWithID:@"proceed-link"]; + } + + [ChromeEarlGrey waitForWebStateContainingText:"hello!"]; +} + +// Starts the dedicated HTTPS test server. +- (void)startHTTPSServer { + _HTTPSServer = std::make_unique<net::test_server::EmbeddedTestServer>( + net::test_server::EmbeddedTestServer::TYPE_HTTPS); + _HTTPSServer->ServeFilesFromDirectory( + base::PathService::CheckedGet(base::DIR_ASSETS) + .AppendASCII("ios/testing/data/http_server_files/")); + RegisterDefaultHandlers(_HTTPSServer.get()); + GREYAssertTrue(_HTTPSServer->Start(), @"HTTPS Test server failed to start."); +} + // Opens the "Other Passwords" screen. - (void)openOtherPasswords { // Bring up the keyboard. @@ -1172,11 +1206,14 @@ // Disable the credential bottom sheet. [CredentialSuggestionBottomSheetAppInterface disableBottomSheet]; + [self startHTTPSServer]; + self.URL = _HTTPSServer->GetURL(kFormHTMLFile); + // Save password for site. NSString* URLString = base::SysUTF8ToNSString(self.URL.spec()); [AutofillAppInterface savePasswordFormForURLSpec:URLString]; - [self loadLoginPage]; + [self loadHTTPSLoginPage]; // Bring up the keyboard. [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()] @@ -1283,11 +1320,15 @@ // Disable the credential bottom sheet. [CredentialSuggestionBottomSheetAppInterface disableBottomSheet]; + [self startHTTPSServer]; + + self.URL = _HTTPSServer->GetURL(kFormHTMLFile); + // Save a credential with a backup password for the current site. NSString* URLString = base::SysUTF8ToNSString(self.URL.spec()); [AutofillAppInterface savePasswordFormWithBackupForURLSpec:URLString]; - [self loadLoginPage]; + [self loadHTTPSLoginPage]; // Bring up the keyboard. [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
Original Bug Report
Potential security bypasses in iOS manual password filler's Autofill Form button
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 https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: The ‘Autofill Form’ button in the iOS manual password filler bypasses HTTP warnings and re-authentication (Face ID/Touch ID) requirements. This allows silent credential injection over insecure connections and potential unauthorized access to credentials after the authentication grace period has expired.
Affected files:
ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_password_cell.mmios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_injection_handler.mm
Estimated timestamp from git blame: 2024-07-24
Summary
The ManualFillPasswordCell in Chrome for iOS contains two potential security logic flaws in its “Autofill Form” button handler (onAutofillFormButtonTapped). This button fails to enforce the same security constraints that are applied when a user manually taps individual credential chips (e.g., username or password chips).
Specifically, it allows credential injection over insecure HTTP connections without displaying a security warning, and it bypasses re-authentication requirements when used from the “All Passwords” context.
Vulnerability Details
1. Protocol Downgrade / HTTP Bypass
In ManualFillPasswordCell.mm, when a user taps an individual password chip, the userDidTapPasswordButton: method is triggered. This method explicitly checks if injection is safe via [self.contentInjector canUserInjectInPasswordField:YES requiresHTTPS:YES]. If the target page is not using HTTPS, a security warning (IDS_IOS_MANUAL_FALLBACK_NOT_SECURE_GENERIC_BODY) is displayed via the securityAlertHandler, and the injection is aborted.
However, the “Autofill Form” button handler, onAutofillFormButtonTapped, directly calls autofillFormWithCredential:shouldReauth:. Neither the cell handler nor the downstream implementation in ManualFillInjectionHandler.mm calls canUserInjectInPasswordField:requiresHTTPS: or performs any other HTTPS check.
Consequently, credentials can be silently injected into insecure HTTP pages via the “Autofill Form” button, bypassing the intentional security warning intended for manual fallback scenarios.
2. Re-authentication (Face ID/Touch ID) Bypass
Manual fill operations are typically protected by re-authentication. When a user selects a credential chip, the ReauthenticationModule is invoked, allowing for a 60-second grace period (canReusePreviousAuth:YES). If this grace period has expired, the user is prompted to re-authenticate (e.g., via Face ID).
The “Autofill Form” button handler bypasses this security measure when the cell is displayed in the “All Passwords” context. In manual_fill_credentials_mediator.mm, when the ManualFillAllPasswordCoordinator displays the full list of saved passwords, the isFromAllPasswordsContext flag is set to YES.
In this scenario, onAutofillFormButtonTapped passes shouldReauth:NO to the injection handler:
[self.contentInjector autofillFormWithCredential:self.credential
shouldReauth:!_fromAllPasswordsContext];
In ManualFillInjectionHandler.mm, passing shouldReauth:NO causes the ReauthenticationModule to be skipped entirely:
if (shouldReauth && [self.reauthenticationModule canAttemptReauth]) {
// ... reauth logic ...
} else {
[self fillFormWithCredential:credential];
}
While tapping an individual password chip in the “All Passwords” list correctly triggers a Face ID prompt once the 60-second grace period expires (via userDidPickContent:), the “Autofill Form” button remains functional indefinitely without further authentication.
Potential Attack Scenario
(Note: These are suggested steps based on static analysis; our tooling agent cannot execute code to confirm.)
- An attacker creates a malicious webpage served over HTTP containing a login form and data-exfiltration JavaScript.
- The attacker gains physical access to the victim’s unlocked iOS device.
- The attacker navigates to their HTTP webpage, focuses a password field, and taps the key icon to open the manual fill keyboard accessory.
- The attacker taps “Other Passwords…” to open the “All Passwords” view. This prompts for Face ID/Passcode. The attacker either knows the passcode or points the device at the nearby victim to pass this initial check.
- The attacker leaves the device on the “All Passwords” screen and waits for more than 60 seconds to expire the
ReauthenticationModule’s grace period. - The attacker taps the “Autofill Form” button on a high-value credential (e.g., a bank account).
- Because
shouldReauthis passed asNO, no re-authentication prompt is shown. Because the HTTPS check is missing, no security warning is shown. - The plaintext credentials are automatically injected into the HTTP page and exfiltrated by the attacker’s script.
Suggested Fix
- Enforce HTTPS Check: In
ManualFillInjectionHandler.mm, updateautofillFormWithCredential:shouldReauth:to callcanUserInjectInPasswordField:YES requiresHTTPS:YESbefore proceeding with the fill operation. If the check fails, the method should return early. - Enforce Re-authentication: Remove the
!_fromAllPasswordsContextlogic fromonAutofillFormButtonTapped. The decision to skip re-authentication should not be based on whether the view is the “All Passwords” list, as the user could dwell on this screen longer than the 60-second grace period.shouldReauthshould always beYESwhen dealing with password injection, or the grace period check should be relied upon exclusively.
Evaluated with Chrome root at commit: cc901875d53bf4e4fe0e01f02843871da4106e70
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.