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

Overview

Low
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in Navigation
DescriptionInappropriate implementation in Navigation
ComponentNavigation
Sub-componentUI>Browser>Navigation
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.
Tracker375550814
Fix commit27dc50bbbf42 (chromium/src) +55/-9
CWECWE-79 (Cross-site scripting)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedKhiem Tran (@duckhiem)
Reported2024-10-25 · bounty $1000 (per vendor advisory)
Fix landed2024-11-04 (public git)
Disclosed2025-01-14
Patch gap71 days — fix public in source before the advisory

Background

Framebusting mitigation
A protection from crbug.com/40084719 that blocks a subframe from navigating the top-level main frame to a cross-origin destination without a user gesture.
Redirect blocked dialog
The FramebustBlockTabHelper UI that surfaces a blocked framebust and offers the user a manual link to proceed to the intended blocked_url.
`RenderProcessHost::FilterURL`
A browser-process sanitizer that rewrites URLs a renderer supplies into safe equivalents, replacing off-limits targets with content::kBlockedURL (about:blank#blocked).
`DidBlockNavigation` IPC
The renderer-to-browser message (handled in RenderFrameHostImpl) that reports a blocked navigation along with the blocked_url and initiator_url the renderer chose.

Root Cause Analysis

When the framebusting mitigation blocked a subframe from navigating the main frame, the renderer reported the intended destination through the DidBlockNavigation path in RenderFrameHostImpl, and the browser stored that blocked_url in the FramebustBlockTabHelper so the user could click to proceed. The browser trusted the renderer-supplied blocked_url and initiator_url verbatim, without passing them through the standard RenderProcessHost::FilterURL sanitization that every other renderer-originated URL undergoes. This violated the invariant that a renderer must never be able to induce a browser-initiated navigation to a URL its own process is not allowed to reach.

As a result, a malicious subframe could name a privileged target such as a chrome:// or file:// URL, and the “proceed” affordance in the dialog would then let a browser-context navigation reach it.

The fix calls process->FilterURL(/*empty_allowed=*/false, ...) on both the blocked_url and the initiator_url before they are recorded, so any off-limits URL is rewritten to content::kBlockedURL and the bypass link becomes harmless.

Key insight
The single mistake was trusting a renderer-supplied navigation URL for a browser-initiated action without running it through RenderProcessHost::FilterURL; the fix restores that sanitization so privileged targets collapse to content::kBlockedURL before they can be offered to the user.

Attack Path

  1. Set up a malicious embedded frame An attacker page embeds a cross-origin subframe (or controls one) that will attempt to navigate the top-level frame.
  2. Trigger a blocked framebust to a privileged URL Without a user gesture the subframe calls top.location = "chrome://settings" (or a file:// URL), which the framebusting mitigation blocks and reports via DidBlockNavigation.
  3. Poison the blocked-URL list The browser stores the unfiltered privileged blocked_url in the FramebustBlockTabHelper, so the redirect-blocked dialog now presents a link to chrome://settings.
  4. Lure the user to proceed The user clicks the offered link, causing a browser-initiated navigation to the privileged URL the renderer could never have reached directly.
  5. Reach off-limits surface The main frame lands on the privileged chrome:// or file:// resource, defeating the process-level navigation restrictions.

Impact Assessment

An attacker gains the ability to steer the top-level frame to privileged chrome:// and file:// URLs that a renderer is normally forbidden from reaching, by laundering the target through the redirect-blocked dialog’s user-visible “proceed” link. The escalation occurs in the browser process’s navigation context but requires a user click on the dialog’s link, so the precondition is a socially-engineered interaction plus an embedded or controlled cross-origin subframe. This is a logic/inappropriate-implementation defect rather than a memory-safety bug, exposing privileged UI and local-file surfaces to renderer-chosen destinations.

Changed Functions

FunctionChangeNotes
IN_PROC_BROWSER_TEST_F
chrome/browser/ui/content_settings/framebust_block_browsertest.cc
modified

Files Changed

  • chrome/browser/ui/content_settings/framebust_block_browsertest.cc
  • content/browser/renderer_host/render_frame_host_impl.cc

Audit Directions

  • Unfiltered renderer URLs on browser-initiated paths
    Search for browser-side handlers that store or navigate to a GURL received from a renderer IPC without calling RenderProcessHost::FilterURL.
  • Deferred-navigation and dialog affordances
    Review UI that lets a user “proceed” to a renderer-supplied URL (interstitials, blocked-content prompts, download and popup dialogs) for the same missing sanitization.
  • Privileged-scheme reachability
    Look for code paths where chrome://, file://, or other off-limits schemes can be introduced by a renderer and later actioned in a more-privileged context.
From 27dc50bbbf4264d20c2b42d0b2aafc9ebd63fbec Mon Sep 17 00:00:00 2001
From: Charlie Reis <creis@chromium.org>
Date: Mon, 04 Nov 2024 19:31:22 +0000
Subject: [PATCH] Filter any URLs passed to the "redirect blocked" dialogs.

The framebusting mitigation from https://crbug.com/40084719 can block
subframes from navigating the main frame cross-origin without a user
gesture. However, the dialog allows the user to manually proceed to the
URL if desired.

This URL was not adequately filtered before, allowing chrome:// and
other privileged URLs to be used even if the renderer could not nomrally
navigate to them.  This CL adds the necessary filtering, which should
apply to both the desktop and Android dialogs.

Bug: 375550814
Change-Id: Icd518a869a06ad982767386d5d7a1528e6179e6c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5973403
Reviewed-by: Patrick Monette <pmonette@chromium.org>
Reviewed-by: Nate Chapin <japhet@chromium.org>
Reviewed-by: Andy Paicu <andypaicu@chromium.org>
Reviewed-by: Liam Brady <lbrady@google.com>
Auto-Submit: Charlie Reis <creis@chromium.org>
Commit-Queue: Charlie Reis <creis@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1377807}
---

diff --git a/chrome/browser/ui/content_settings/framebust_block_browsertest.cc b/chrome/browser/ui/content_settings/framebust_block_browsertest.cc
index 8ad757a..6137a4f8 100644
--- a/chrome/browser/ui/content_settings/framebust_block_browsertest.cc
+++ b/chrome/browser/ui/content_settings/framebust_block_browsertest.cc
@@ -33,6 +33,7 @@
 #include "components/content_settings/core/common/content_settings_types.h"
 #include "content/public/common/content_features.h"
 #include "content/public/common/isolated_world_ids.h"
+#include "content/public/common/url_constants.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/fenced_frame_test_util.h"
@@ -114,8 +115,16 @@
   }
 
   bool ExecuteAndCheckBlockedRedirection() {
-    EXPECT_TRUE(ui_test_utils::NavigateToURL(
-        browser(), embedded_test_server()->GetURL("/iframe.html")));
+    return ExecuteAndCheckBlockedRedirection(
+        embedded_test_server()->GetURL("b.com", "/title1.html"));
+  }
+
+  // Attempts to framebust to `redirect_url` and ensures the navigation is
+  // blocked. (The test fails if not.) Returns whether the blocked URL is added
+  // to the tab helper, where the user can proceed to it if desired.
+  bool ExecuteAndCheckBlockedRedirection(const GURL& redirect_url) {
+    const GURL original_url = embedded_test_server()->GetURL("/iframe.html");
+    EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), original_url));
 
     const GURL child_url =
         embedded_test_server()->GetURL("a.com", "/title1.html");
@@ -125,9 +134,6 @@
         content::ChildFrameAt(GetWebContents()->GetPrimaryMainFrame(), 0);
     EXPECT_EQ(child_url, child->GetLastCommittedURL());
 
-    const GURL redirect_url =
-        embedded_test_server()->GetURL("b.com", "/title1.html");
-
     base::RunLoop block_waiter;
     blocked_url_added_closure_ = block_waiter.QuitClosure();
     child->ExecuteJavaScriptForTests(
@@ -136,6 +142,12 @@
         base::NullCallback(), content::ISOLATED_WORLD_ID_GLOBAL);
     block_waiter.Run();
 
+    // Ensure we have not left the original page.
+    EXPECT_EQ(original_url, GetWebContents()->GetLastCommittedURL());
+
+    // Return whether the redirect URL itself ended up in the list of blocked
+    // URLs, which only happens if the renderer had the ability to navigate to
+    // the URL in the first place.
     return base::Contains(GetFramebustTabHelper()->blocked_urls(),
                           redirect_url);
   }
@@ -152,8 +164,9 @@
 // to that URL.
 IN_PROC_BROWSER_TEST_F(FramebustBlockBrowserTest, ModelAllowsRedirection) {
   const GURL blocked_urls[] = {
-      GURL(chrome::kChromeUIHistoryURL), GURL(chrome::kChromeUISettingsURL),
-      GURL(chrome::kChromeUIVersionURL),
+      embedded_test_server()->GetURL("b.com", "/title1.html"),
+      embedded_test_server()->GetURL("c.com", "/title1.html"),
+      embedded_test_server()->GetURL("d.com", "/title1.html"),
   };
 
   // Signal that a blocked redirection happened.
@@ -183,7 +196,8 @@
   EXPECT_TRUE(clicked_index_.has_value());
   EXPECT_TRUE(clicked_url_.has_value());
   EXPECT_EQ(1u, clicked_index_.value());
-  EXPECT_EQ(GURL(chrome::kChromeUISettingsURL), clicked_url_.value());
+  EXPECT_EQ(embedded_test_server()->GetURL("c.com", "/title1.html"),
+            clicked_url_.value());
   EXPECT_FALSE(helper->HasBlockedUrls());
   EXPECT_EQ(blocked_urls[1], GetWebContents()->GetLastCommittedURL());
 }
@@ -288,6 +302,30 @@
   EXPECT_TRUE(ExecuteAndCheckBlockedRedirection());
 }
 
+// Attempts to navigate to chrome:// URLs should be blocked without allowing the
+// user to proceed. Instead, the blocked URLs list includes content:kBlockedURL,
+// which is about:blank#blocked, similar to other cases where a renderer
+// attempts to navigate to an off-limits URL. See https://crbug.com/375550814.
+IN_PROC_BROWSER_TEST_F(FramebustBlockBrowserTest,
+                       Framebust_WebUI_Blocked_No_Bypass) {
+  const GURL chrome_url(chrome::kChromeUISettingsURL);
+  EXPECT_FALSE(ExecuteAndCheckBlockedRedirection(chrome_url));
+  EXPECT_TRUE(base::Contains(GetFramebustTabHelper()->blocked_urls(),
+                             GURL(content::kBlockedURL)));
+}
+
+// Attempts to navigate to file:// URLs should be blocked without allowing the
+// user to proceed. Instead, the blocked URLs list includes content:kBlockedURL,
+// which is about:blank#blocked, similar to other cases where a renderer
+// attempts to navigate to an off-limits URL. See https://crbug.com/375550814.
+IN_PROC_BROWSER_TEST_F(FramebustBlockBrowserTest,
+                       Framebust_File_Blocked_No_Bypass) {
+  const GURL file_url("file:///");
+  EXPECT_FALSE(ExecuteAndCheckBlockedRedirection(file_url));
+  EXPECT_TRUE(base::Contains(GetFramebustTabHelper()->blocked_urls(),
+                             GURL(content::kBlockedURL)));
+}
+
 IN_PROC_BROWSER_TEST_F(FramebustBlockBrowserTest,
                        FramebustAllowedByGlobalSetting) {
   HostContentSettingsMap* settings_map =
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index b7122241..21d7324 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -7674,10 +7674,18 @@
     const GURL& blocked_url,
     const GURL& initiator_url,
     blink::mojom::NavigationBlockedReason reason) {
+  // Do not allow renderers to show off-limits URLs in the blocked dialog.
+  GURL validated_blocked_url = blocked_url;
+  GURL validated_initiator_url = initiator_url;
+  RenderProcessHost* process = GetProcess();
+  process->FilterURL(/*empty_allowed=*/false, &validated_blocked_url);
+  process->FilterURL(/*empty_allowed=*/false, &validated_initiator_url);
+
   // Cross-origin navigations are not allowed in prerendering so we can not
   // reach here while prerendering.
   DCHECK_NE(lifecycle_state(), LifecycleStateImpl::kPrerendering);
-  delegate_->OnDidBlockNavigation(blocked_url, initiator_url, reason);
+  delegate_->OnDidBlockNavigation(validated_blocked_url,
+                                  validated_initiator_url, reason);
 }
 
 void RenderFrameHostImpl::DidChangeLoadProgress(double load_progress) {
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/ui/content_settings/framebust_block_browsertest.cc b/chrome/browser/ui/content_settings/framebust_block_browsertest.cc
index 8ad757a..6137a4f8 100644
--- a/chrome/browser/ui/content_settings/framebust_block_browsertest.cc
+++ b/chrome/browser/ui/content_settings/framebust_block_browsertest.cc
@@ -33,6 +33,7 @@
 #include "components/content_settings/core/common/content_settings_types.h"
 #include "content/public/common/content_features.h"
 #include "content/public/common/isolated_world_ids.h"
+#include "content/public/common/url_constants.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/fenced_frame_test_util.h"
@@ -114,8 +115,16 @@
   }
 
   bool ExecuteAndCheckBlockedRedirection() {
-    EXPECT_TRUE(ui_test_utils::NavigateToURL(
-        browser(), embedded_test_server()->GetURL("/iframe.html")));
+    return ExecuteAndCheckBlockedRedirection(
+        embedded_test_server()->GetURL("b.com", "/title1.html"));
+  }
+
+  // Attempts to framebust to `redirect_url` and ensures the navigation is
+  // blocked. (The test fails if not.) Returns whether the blocked URL is added
+  // to the tab helper, where the user can proceed to it if desired.
+  bool ExecuteAndCheckBlockedRedirection(const GURL& redirect_url) {
+    const GURL original_url = embedded_test_server()->GetURL("/iframe.html");
+    EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), original_url));
 
     const GURL child_url =
         embedded_test_server()->GetURL("a.com", "/title1.html");
@@ -125,9 +134,6 @@
         content::ChildFrameAt(GetWebContents()->GetPrimaryMainFrame(), 0);
     EXPECT_EQ(child_url, child->GetLastCommittedURL());
 
-    const GURL redirect_url =
-        embedded_test_server()->GetURL("b.com", "/title1.html");
-
     base::RunLoop block_waiter;
     blocked_url_added_closure_ = block_waiter.QuitClosure();
     child->ExecuteJavaScriptForTests(
@@ -136,6 +142,12 @@
         base::NullCallback(), content::ISOLATED_WORLD_ID_GLOBAL);
     block_waiter.Run();
 
+    // Ensure we have not left the original page.
+    EXPECT_EQ(original_url, GetWebContents()->GetLastCommittedURL());
+
+    // Return whether the redirect URL itself ended up in the list of blocked
+    // URLs, which only happens if the renderer had the ability to navigate to
+    // the URL in the first place.
     return base::Contains(GetFramebustTabHelper()->blocked_urls(),
                           redirect_url);
   }
@@ -152,8 +164,9 @@
 // to that URL.
 IN_PROC_BROWSER_TEST_F(FramebustBlockBrowserTest, ModelAllowsRedirection) {
   const GURL blocked_urls[] = {
-      GURL(chrome::kChromeUIHistoryURL), GURL(chrome::kChromeUISettingsURL),
-      GURL(chrome::kChromeUIVersionURL),
+      embedded_test_server()->GetURL("b.com", "/title1.html"),
+      embedded_test_server()->GetURL("c.com", "/title1.html"),
+      embedded_test_server()->GetURL("d.com", "/title1.html"),
   };
 
   // Signal that a blocked redirection happened.
@@ -183,7 +196,8 @@
   EXPECT_TRUE(clicked_index_.has_value());
   EXPECT_TRUE(clicked_url_.has_value());
   EXPECT_EQ(1u, clicked_index_.value());
-  EXPECT_EQ(GURL(chrome::kChromeUISettingsURL), clicked_url_.value());
+  EXPECT_EQ(embedded_test_server()->GetURL("c.com", "/title1.html"),
+            clicked_url_.value());
   EXPECT_FALSE(helper->HasBlockedUrls());
   EXPECT_EQ(blocked_urls[1], GetWebContents()->GetLastCommittedURL());
 }
@@ -288,6 +302,30 @@
   EXPECT_TRUE(ExecuteAndCheckBlockedRedirection());
 }
 
+// Attempts to navigate to chrome:// URLs should be blocked without allowing the
+// user to proceed. Instead, the blocked URLs list includes content:kBlockedURL,
+// which is about:blank#blocked, similar to other cases where a renderer
+// attempts to navigate to an off-limits URL. See https://crbug.com/375550814.
+IN_PROC_BROWSER_TEST_F(FramebustBlockBrowserTest,
+                       Framebust_WebUI_Blocked_No_Bypass) {
+  const GURL chrome_url(chrome::kChromeUISettingsURL);
+  EXPECT_FALSE(ExecuteAndCheckBlockedRedirection(chrome_url));
+  EXPECT_TRUE(base::Contains(GetFramebustTabHelper()->blocked_urls(),
+                             GURL(content::kBlockedURL)));
+}
+
+// Attempts to navigate to file:// URLs should be blocked without allowing the
+// user to proceed. Instead, the blocked URLs list includes content:kBlockedURL,
+// which is about:blank#blocked, similar to other cases where a renderer
+// attempts to navigate to an off-limits URL. See https://crbug.com/375550814.
+IN_PROC_BROWSER_TEST_F(FramebustBlockBrowserTest,
+                       Framebust_File_Blocked_No_Bypass) {
+  const GURL file_url("file:///");
+  EXPECT_FALSE(ExecuteAndCheckBlockedRedirection(file_url));
+  EXPECT_TRUE(base::Contains(GetFramebustTabHelper()->blocked_urls(),
+                             GURL(content::kBlockedURL)));
+}
+
 IN_PROC_BROWSER_TEST_F(FramebustBlockBrowserTest,
                        FramebustAllowedByGlobalSetting) {
   HostContentSettingsMap* settings_map =
Loading diff…

Original Bug Report

reported by du...@gmail.com

On Chrome 130.0.6723.73 on Android: there is a way website A can automatically opens chrome://chrome-urls well via Redirect blocked function

Security Bug

Important: Please do not change the component of this bug manually.

Please READ THIS FAQ before filing a bug: https://chromium.googlesource.com/chromium/src/+/HEAD/docs/security/faq.md

Please see the following link for instructions on filing security bugs: https://www.chromium.org/Home/chromium-security/reporting-security-bugs

Reports may be eligible for reward payments under the Chrome VRP: https://g.co/chrome/vrp

NOTE: Security bugs are normally made public once a fix has been widely deployed.


VULNERABILITY DETAILS On Chrome 130.0.6723.73 on Android: there is a way website A can automatically opens chrome://chrome-urls well via Redirect blocked function.

VERSION Chrome Version: 130.0.6723.73 + [stable] Operating System: [Android 14]

REPRODUCTION CASE Please include a demonstration of the security bug, such as an attached HTML or binary file that reproduces the bug when loaded in Chrome. PLEASE make the file as small as possible and remove any content not required to demonstrate the bug, or any personal or confidential information.

Please attach files directly, not in zip or other archive formats, and if you’ve created a demonstration site please also attach the files needed to reproduce the demonstration locally.

Open the local .html file (attached), allow in the Redirect blocked notification, the URL chrome://chrome-urls can be opened well after that from the local .html file via Redirect blocked function.

FOR CRASHES, PLEASE INCLUDE THE FOLLOWING ADDITIONAL INFORMATION Type of crash: [tab, browser, etc.] Crash State: [see link above: stack trace with symbols, registers, exception record] Client ID (if relevant): [see link above]

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: Khiem Tran

View on issue tracker