Medium watch CVSS 8.8 chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in Intents
DescriptionInappropriate implementation in Intents
ComponentIntents
Sub-componentBlink>Web Authentication (Passkeys & Security Keys)
Bug ClassLogic Error
Action tierwatch — logic/policy bug — security-relevant, no memory-unsafety
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker401823929
Fix commit720a32bd47d3 (chromium/src) +21/-3
CWECWE-20 (Improper input validation)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedSimon Rawet
Reported2025-03-09 · bounty $2000 (per vendor advisory)
Fix landed2025-03-20 (public git)
Disclosed2025-04-01
Patch gap12 days — fix public in source before the advisory

Background

Intent URI / external navigation
On Android, a web page can request that Chrome hand off a navigation to another app via an intent: (or scheme-bearing) URI, which ExternalNavigationHandler parses into an Intent and dispatches.
`Intent` scheme
The URI scheme carried in the intent’s data (intent.getData().getScheme()), which Android treats as case-sensitive, so w3irD and w3ird are distinct scheme strings.
`normalizeScheme()`
The Android Uri method that returns the URI with its scheme lower-cased, used here to canonicalize the intent’s data before dispatch.
`intent.setSelector(null)`
A pre-existing hardening step in the same code that clears the intent selector so an intent cannot bypass intent filters (crbug.com/1254422), i.e. the surrounding code already sanitizes the outgoing intent.

Root Cause Analysis

When preparing an external intent for dispatch, the handler cleared the selector but passed the intent’s data through with its scheme exactly as supplied by the page, and the removed test comment states the old expectation explicitly: “Schemes on Android are case-sensitive, so ensure the scheme is passed through as-is,” with the intent’s scheme preserved as w3irD. Because Android compares schemes case-sensitively, any allow/deny or filter logic keyed on a lower-cased scheme name could be evaded by supplying a mixed-case scheme (e.g. W3IRD vs w3ird), so a page-controlled intent could reach an app or resolution path that a canonical-case comparison would have excluded.

The fix, gated behind kLowerCaseIntentSchemes / LOWER_CASE_INTENT_SCHEMES, adds a block that, when the feature is enabled and intent.getData() is non-null, rewrites the intent via intent.setDataAndType(intent.getData().normalizeScheme(), intent.getType()) so the scheme is normalized to lower case before dispatch. The updated test now asserts the scheme is w3ird, confirming the intent that leaves Chrome carries a canonicalized scheme, closing the case-sensitivity gap; the feature flag exists as a kill switch in case normalization breaks legitimate apps.

Key insight
The core mistake was forwarding a page-supplied intent scheme verbatim, leaving case-sensitivity as a bypass of scheme-based filtering; the fix normalizes the scheme to lower case with normalizeScheme() before the intent is dispatched.

Attack Path

  1. Craft a mixed-case intent URI A malicious web page constructs an external navigation whose intent data uses a non-canonical (mixed-case) scheme designed to differ from the lower-cased form used by downstream checks.
  2. Trigger external navigation The page navigates so Chrome routes the request through ExternalNavigationHandler, which builds the Intent from the URI.
  3. Bypass scheme-keyed handling Pre-patch, the intent’s data scheme is passed through as-is after setSelector(null), so any scheme-based allow/deny/routing that assumes canonical casing is evaluated against the un-normalized scheme and can be evaded (inferred).
  4. Intent dispatched with attacker casing The intent is launched with the original scheme casing, potentially resolving to an app or handling path the browser’s checks intended to exclude. The specific downstream app/resource reached is (inferred).

Impact Assessment

An attacker-controlled web page could influence how an external intent’s scheme is interpreted by supplying non-canonical casing, potentially reaching an intent-handling path that scheme-based filtering was meant to block, on Android via the external-navigation flow. The advisory classifies this as an inappropriate implementation (logic error) in Intents and does not report in-the-wild exploitation; the concrete target reached and any consequent app-level effect are (inferred). The fix ships enabled by default (M136) behind the LowerCaseIntentSchemes kill switch, and requires the user/page to initiate an external navigation carrying an intent with data.

Files Changed

  • components/external_intents/android/external_intents_features.cc
  • components/external_intents/android/external_intents_features.h
  • components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalIntentsFeatures.java
  • components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java
  • components/external_intents/android/javatests/src/org/chromium/components/external_intents/ExternalNavigationHandlerTest.java

Audit Directions

  • Case-sensitive vs case-insensitive scheme handling
    Audit other places that compare, filter, or route on URI/intent schemes to ensure both sides use the same canonical (lower-cased) form, since Android schemes are case-sensitive while many checks assume lower case.
  • Page-supplied data forwarded to Android IPC
    Review paths that build Intents from web-controlled URIs and pass data/type/action/component through without normalization, following the existing setSelector(null) hardening pattern.
  • Normalization gated behind flags
    When a fix normalizes untrusted input behind a kill-switch feature, verify no earlier check relies on the un-normalized value and that the default-enabled path is the safe one.
From 720a32bd47d3188bbf824743147bc17de31de8c4 Mon Sep 17 00:00:00 2001
From: Ken Buchanan <kenrb@chromium.org>
Date: Thu, 20 Mar 2025 11:10:50 -0700
Subject: [PATCH] Normalize intent URIs for external navigations

This is a follow-up to
https://chromium-review.googlesource.com/c/chromium/src/+/6367258.

Intent URIs will now be normalized to lower case. There is a flag to
act as a kill switch if any breakage results.

Bug: 401823929
Change-Id: I9346c8ed2f80469c81bebfe192d9a88d1dfebcae
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6367784
Commit-Queue: Ken Buchanan <kenrb@chromium.org>
Reviewed-by: Michael Thiessen <mthiesse@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1435559}
---

diff --git a/components/external_intents/android/external_intents_features.cc b/components/external_intents/android/external_intents_features.cc
index 6c81905..afae110f 100644
--- a/components/external_intents/android/external_intents_features.cc
+++ b/components/external_intents/android/external_intents_features.cc
@@ -26,7 +26,7 @@
 // Array of features exposed through the Java ExternalIntentsFeatures API.
 const base::Feature* const kFeaturesExposedToJava[] = {
     &kExternalNavigationDebugLogs, &kBlockFrameRenavigations,
-    &kBlockIntentsToSelf};
+    &kBlockIntentsToSelf, &kLowerCaseIntentSchemes};
 
 }  // namespace
 
@@ -44,6 +44,11 @@
              "BlockIntentsToSelf",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+// Enabled by default in M136.
+BASE_FEATURE(kLowerCaseIntentSchemes,
+             "LowerCaseIntentSchemes",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 static jlong JNI_ExternalIntentsFeatures_GetFeature(JNIEnv* env, jint ordinal) {
   return reinterpret_cast<jlong>(kFeaturesExposedToJava[ordinal]);
 }
diff --git a/components/external_intents/android/external_intents_features.h b/components/external_intents/android/external_intents_features.h
index 1c7535d..9644a25 100644
--- a/components/external_intents/android/external_intents_features.h
+++ b/components/external_intents/android/external_intents_features.h
@@ -13,6 +13,9 @@
 BASE_DECLARE_FEATURE(kBlockFrameRenavigations);
 BASE_DECLARE_FEATURE(kBlockIntentsToSelf);
 
+// When enabled, forces intent schemes to be lower case.
+BASE_DECLARE_FEATURE(kLowerCaseIntentSchemes);
+
 }  // namespace external_intents
 
 #endif  // COMPONENTS_EXTERNAL_INTENTS_ANDROID_EXTERNAL_INTENTS_FEATURES_H_
diff --git a/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalIntentsFeatures.java b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalIntentsFeatures.java
index e2e4578..fab0065 100644
--- a/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalIntentsFeatures.java
+++ b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalIntentsFeatures.java
@@ -23,6 +23,7 @@
     public static final String EXTERNAL_NAVIGATION_DEBUG_LOGS_NAME = "ExternalNavigationDebugLogs";
     public static final String BLOCK_FRAME_RENAVIGATIONS_NAME = "BlockFrameRenavigations3";
     public static final String BLOCK_INTENTS_TO_SELF_NAME = "BlockIntentsToSelf";
+    public static final String LOWER_CASE_INTENT_SCHEMES_NAME = "LowerCaseIntentSchemes";
 
     public static final ExternalIntentsFeatures EXTERNAL_NAVIGATION_DEBUG_LOGS =
             new ExternalIntentsFeatures(0, EXTERNAL_NAVIGATION_DEBUG_LOGS_NAME);
@@ -33,6 +34,9 @@
     public static final ExternalIntentsFeatures BLOCK_INTENTS_TO_SELF =
             new ExternalIntentsFeatures(2, BLOCK_INTENTS_TO_SELF_NAME);
 
+    public static final ExternalIntentsFeatures LOWER_CASE_INTENT_SCHEMES =
+            new ExternalIntentsFeatures(3, LOWER_CASE_INTENT_SCHEMES_NAME);
+
     private final int mOrdinal;
 
     private ExternalIntentsFeatures(int ordinal, String name) {
diff --git a/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java
index c7ebed0..58e81d3 100644
--- a/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java
+++ b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java
@@ -1824,6 +1824,12 @@
         // Intent Selectors allow intents to bypass the intent filter and potentially send apps URIs
         // they were not expecting to handle. https://crbug.com/1254422
         intent.setSelector(null);
+
+        // Intent schemes should be normalized to lower case. https://crbug.com/401823929
+        if (ExternalIntentsFeatures.LOWER_CASE_INTENT_SCHEMES.isEnabled()
+                && intent.getData() != null) {
+            intent.setDataAndType(intent.getData().normalizeScheme(), intent.getType());
+        }
     }
 
     /**
diff --git a/components/external_intents/android/javatests/src/org/chromium/components/external_intents/ExternalNavigationHandlerTest.java b/components/external_intents/android/javatests/src/org/chromium/components/external_intents/ExternalNavigationHandlerTest.java
index ce43bf8..ae11749 100644
--- a/components/external_intents/android/javatests/src/org/chromium/components/external_intents/ExternalNavigationHandlerTest.java
+++ b/components/external_intents/android/javatests/src/org/chromium/components/external_intents/ExternalNavigationHandlerTest.java
@@ -1999,8 +1999,8 @@
                 .expecting(
                         OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
                         START_OTHER_ACTIVITY);
-        // Schemes on Android are case-sensitive, so ensure the scheme is passed through as-is.
-        Assert.assertEquals("w3irD", mUrlHandler.mStartActivityIntent.getScheme());
+        // Intent schemes should be normalized.
+        Assert.assertEquals("w3ird", mUrlHandler.mStartActivityIntent.getScheme());
     }
 
     @Test
Loading diff…

Original Bug Report

reported by Si...@rawet.se

intent:// can bypass fido:/ URI bock (see: 370482421)

VULNERABILITY DETAILS It’s possbile to bypass the fix for 370482421 by using a intent URI.

VERSION Chrome Version: 133.0.6943.137 stable Operating System: Pixel 7 Pro Android 15 March security updates.

REPRODUCTION CASE See https://issues.chromium.org/issues/370482421 for the original discussion. TL;DR: A website should not be allowed to open FIDO:/ URLs on mobile devices.

The issue is that the original fix was never tested on intent:// URLs. The original fix is a case sensitive check on the intent, it checks for “fido” and not “FIDO”. I have attached a html file whit a POC link that will start FIDO activity when clicked. The file also includes links that are blocked and the console output when clicked.

The fix seams to be fairly straight forward. Use a case insensitive match like what “hasFileSchemeInIntentURI” dose. See attached diff.

CREDIT INFORMATION Externally reported security bugs may appear in Chrome release notes. If this bug is included, how would you like to be credited? Reporter credit: [goes here]

View on issue tracker
Uploaded PoC files