Medium CVSS 4.3 firefox Spoofing 🔧 Commit mapped

Overview

Medium
Severity
4.3
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionThe incorrect domain may have been displayed in the address bar during an interrupted navigation attempt. This could have led to user confusion and possible spoofing attacks.
ComponentCore
Bug ClassSpoofing
Tracker1914797
Fix commit96dc6686ad44 (firefox) +7/-2
CWECWE-290
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N
CISA KEVNot listed
CreditedDaniel Holbert
Disclosed2024-11-26

Background

`GeckoEngineSession`
the Android Components wrapper around a Gecko rendering session that bridges Gecko delegate callbacks into the app’s observer/store model.
`onLocationChange`
the observer notification that updates the URL the app considers current (and thus what the address bar displays) for a session.
`onPageStart`
the Gecko ProgressDelegate callback fired when a page load begins, where progress and loading state are propagated to observers.
Interrupted navigation
a load that begins for a target URL but is cancelled or redirected (for example a scheme link that opens an external-app prompt), leaving the display state to be reconciled.

Root Cause Analysis

When a navigation started in GeckoEngineSession, the onPageStart callback notified observers with onProgress(PROGRESS_START) and onLoadingStateChange(true) but did not immediately notify them of the destination URL via onLocationChange. The invariant that the store’s displayed URL must always correspond to the load actually in progress was therefore violated during the window between the start of a load and the eventual (or never-arriving) location update. If the navigation was interrupted — such as a link that triggered an external-app prompt that the user then cancelled — the address bar could be left showing the previous or intended origin rather than the URL truly reflecting session state, enabling a mismatch between displayed and effective context.

The fix adds onLocationChange(url, hasUserGesture = false) inside the same onPageStart notification block so the store’s URL is updated synchronously at load start, keeping the address bar consistent even when the load is aborted.

Key insight
The core mistake was deferring the URL/location update relative to the loading-state update, so onPageStart announced that a new load had begun without announcing which URL it was for; the fix eliminates that gap by emitting onLocationChange in the same notification block as onLoadingStateChange.

Attack Path

  1. Craft a spoofing target An attacker hosts a page containing a link to a scheme or resource that will trigger an interruptible navigation (for example a vnd.youtube:// style external-app link) from an origin the victim trusts visually.
  2. Victim initiates the navigation The victim taps the link, onPageStart fires and the loading state flips, but no onLocationChange is emitted, so the displayed URL does not yet reflect the real load target.
  3. Navigation is interrupted The load is diverted to an external-app prompt (or otherwise cancelled), and the victim declines it, so no committed location update ever arrives to correct the address bar.
  4. Stale address bar persists The address bar continues to display an incorrect domain that does not match the session’s actual state, giving the victim a false sense of which site they are viewing.
  5. Deception is leveraged The attacker exploits the mismatched address bar to make content appear to originate from a trusted domain, supporting a phishing or spoofing attack.

Impact Assessment

An attacker gains the ability to display an incorrect domain in the address bar of the mobile browser UI (Fenix/GeckoView-based Android app) during an interrupted navigation, enabling user confusion and address-bar spoofing. The precondition is that the victim initiates a navigation that is then interrupted or cancelled before a committed location change is delivered. The impact is rated moderate because it does not grant code execution or data access on its own, only a UI-integrity/spoofing primitive that must be combined with social engineering.

Files Changed

  • mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngineSession.kt
  • mobile/android/android-components/docs/changelog.md
  • mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsAdvancedTest.kt

Audit Directions

  • Deferred UI-state updates
    Look for callbacks that announce a state transition (loading started, progress) without synchronously announcing the associated identity (URL/origin), leaving a reconciliation gap.
  • Interrupted/cancelled navigation paths
    Audit code paths where a navigation can be aborted, redirected, or handed to an external handler, and verify the address bar and session URL are corrected on every abort branch.
  • Observer notification ordering
    Review notifyObservers blocks and delegate bridges for cases where onLoadingStateChange or progress events are emitted without a paired onLocationChange, which can desynchronize displayed and actual state.
diff --git a/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngineSession.kt b/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngineSession.kt
index ddfe9b33c2b..e77f1f79cf2 100644
--- a/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngineSession.kt
+++ b/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngineSession.kt
@@ -1329,6 +1329,7 @@ class GeckoEngineSession(
             notifyObservers {
                 onProgress(PROGRESS_START)
                 onLoadingStateChange(true)
+                onLocationChange(url, hasUserGesture = false)
             }
         }
 
diff --git a/mobile/android/android-components/docs/changelog.md b/mobile/android/android-components/docs/changelog.md
index b396b012b5a..05edc9a41ff 100644
--- a/mobile/android/android-components/docs/changelog.md
+++ b/mobile/android/android-components/docs/changelog.md
@@ -215,6 +215,9 @@ permalink: /changelog/
 * **feature-customtabs**
   * Fallback behaviour when failing to open a new window in custom tab will now be loading the URL directly in the same custom tab. [Bug 1832357](https://bugzilla.mozilla.org/show_bug.cgi?id=1832357)
 
+* **feature-session**
+  * Update URL in the store immediately during onPageStart.
+
 * **tooling-lint**
   * Added a lint rule to detect when `Response#close` may not have been called. Note: Currently, this rule only runs on Android Components.
 
diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsAdvancedTest.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsAdvancedTest.kt
index 93aa24ce80f..d19425c5a62 100644
--- a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsAdvancedTest.kt
+++ b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsAdvancedTest.kt
@@ -31,6 +31,7 @@ class SettingsAdvancedTest : TestSetup() {
     private val youTubeSchemaLink = itemContainingText("Youtube schema link")
     private val playStoreLink = itemContainingText("Playstore link")
     private val playStoreUrl = "play.google.com"
+    private val youtubeUrl = "vnd.youtube://Mozilla"
 
     @get:Rule
     val activityIntentTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides()
@@ -138,7 +139,7 @@ class SettingsAdvancedTest : TestSetup() {
             clickPageObject(youTubeSchemaLink)
             verifyOpenLinkInAnotherAppPrompt()
             clickPageObject(itemWithResIdAndText("android:id/button2", "CANCEL"))
-            verifyUrl(externalLinksPage.url.toString())
+            verifyUrl(youtubeUrl)
         }
     }
 
@@ -207,7 +208,7 @@ class SettingsAdvancedTest : TestSetup() {
                 pageObject = youTubeSchemaLink,
             )
             clickPageObject(itemWithResIdAndText("android:id/button2", "CANCEL"))
-            verifyUrl(externalLinksPage.url.toString())
+            verifyUrl(youtubeUrl)
         }
     }
 
Loading diff…