Low watch CVSS 6.5 chrome Spoofing 📄 Reporter bug report 🔧 Commit mapped

Overview

Low
Severity
6.5
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect security UI in Permissions
DescriptionIncorrect security UI in Permissions
ComponentPermissions
Bug ClassSpoofing
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.
Tracker483899628
Fix commit848a7a9f1fee (chromium/src) +45/-4
CWECWE-451
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N
CISA KEVNot listed
Crediteddaffainfo
Reported2026-02-12 (per vendor advisory)
Fix landed2026-02-19 (public git)
Disclosed2026-04-07
Patch gap47 days — fix public in source before the advisory

Background

Pop-up blocked bubble
the content-settings UI (ContentSettingBubbleModel for ContentSettingsType::POPUPS) that lists URLs Chrome prevented a page from opening in a new window.
URL userinfo component
the optional username:password@ segment permitted before the host in a URL, e.g. http://google.com:[email protected], where the real host is evil.com.
`GURL::spec()`
returns the full canonical URL string including the userinfo component, with no elision or security-oriented formatting.
`url_formatter::FormatUrl`
the display-formatting helper that applies flags such as kFormatUrlOmitUsernamePassword to produce a human-facing, non-spoofable URL string.

Root Cause Analysis

The helper that builds each blocked-popup list-item title (enclosing function name is not shown in the hunk; the @@ context only exposes its const GURL& url parameter and an “Empty URLs should get a placeholder” comment) formed the title directly from url.spec(). Because GURL::spec() emits the raw canonical URL including any username:password@ userinfo, an attacker-controlled popup target such as http://google.com:[email protected] produced a title beginning with google.com, visually implying the popup came from google.com when the actual host is evil.com. The displayed string thus violated the invariant that security-facing UI must reflect the true origin/host, not attacker-supplied userinfo.

The fix replaces base::UTF8ToUTF16(url.spec()) with url_formatter::FormatUrl(url, url_formatter::kFormatUrlOmitUsernamePassword, ...), which strips the userinfo so the displayed title shows the genuine host. The shipped regression test confirms the intended behavior: after the fix the title must not contain google.com and must contain evil.com.

Key insight
The core mistake was rendering a raw GURL::spec() string in security UI, letting the username:password@ userinfo masquerade as the host; the fix routes the URL through url_formatter::FormatUrl with kFormatUrlOmitUsernamePassword so the userinfo is omitted before display.

Attack Path

  1. Host a page that triggers a blocked popup The attacker serves a page that calls window.open with a crafted target, which Chrome’s pop-up blocker intercepts and records.
  2. Embed a spoofing userinfo in the popup URL The target is http://google.com:[email protected], placing a trusted-looking brand in the username:password@ userinfo while evil.com is the real host.
  3. User opens the "Pop-ups blocked" bubble The blocked-item title is built from url.spec(), so it renders beginning with google.com, hiding the true destination.
  4. User is misled about the popup source Believing the blocked popup belongs to google.com, the user may choose to allow/open it, navigating to attacker-controlled evil.com.

Impact Assessment

An attacker gains an origin/URL spoof in the pop-up-blocked security UI of the browser process, misrepresenting which site a blocked popup targets; the only precondition is luring a user to a page that triggers the pop-up blocker and getting them to inspect or act on the bubble. The vendor advisory rates this low severity (“Incorrect security UI in Permissions”) and reports no in-the-wild exploitation. No memory corruption or escalation is demonstrated by the patch; impact is confined to UI deception.

Changed Functions

FunctionChangeNotes
TEST_F
chrome/browser/ui/content_settings/content_setting_bubble_model_unittest.cc
modified

Files Changed

  • chrome/browser/ui/content_settings/content_setting_bubble_model.cc
  • chrome/browser/ui/content_settings/content_setting_bubble_model_unittest.cc

Audit Directions

  • Raw `spec()`/`GetContent()` in UI
    Audit any security- or user-facing surface (bubbles, dialogs, notifications, permission prompts) that builds display strings from GURL::spec(), host() alone, or unformatted URLs instead of url_formatter::FormatUrl/FormatUrlForSecurityDisplay.
  • Userinfo-bearing URLs
    Look for display paths that do not set kFormatUrlOmitUsernamePassword, since username:password@host is a classic vector for host spoofing in any truncated or brand-prominent label.
  • Content-settings and blocked-content lists
    Review other ContentSettingBubbleModel list-item and subtitle builders (not only popups) for the same raw-spec pattern.
From 848a7a9f1feeba1debc2abfc9b9b1b38ec2b28d1 Mon Sep 17 00:00:00 2001
From: Nathan Parker <[email protected]>
Date: Thu, 19 Feb 2026 08:18:27 -0800
Subject: [PATCH] Fix URL spoofing on "Pop-ups blocked" dialog in Chrome.

Switched to FormatUrl() which removes the username:password@ component.

Example of new formatting, with hover: http://screen/M4wV4PuQ4vu2zDr

Bug: 483899628
Change-Id: Iff473a1718f7f4662abddbeff29223973c7de845
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7577181
Reviewed-by: Ravjit Uppal <[email protected]>
Commit-Queue: Nathan Parker <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1587161}
---

diff --git a/chrome/browser/ui/content_settings/content_setting_bubble_model.cc b/chrome/browser/ui/content_settings/content_setting_bubble_model.cc
index 306b3d8..45fc4ca6 100644
--- a/chrome/browser/ui/content_settings/content_setting_bubble_model.cc
+++ b/chrome/browser/ui/content_settings/content_setting_bubble_model.cc
@@ -68,6 +68,7 @@
 #include "components/subresource_filter/core/browser/subresource_filter_constants.h"
 #include "components/subresource_filter/core/browser/subresource_filter_features.h"
 #include "components/url_formatter/elide_url.h"
+#include "components/url_formatter/url_formatter.h"
 #include "components/vector_icons/vector_icons.h"
 #include "content/public/browser/page.h"
 #include "content/public/browser/permission_controller.h"
@@ -171,10 +172,12 @@
                                                       const GURL& url) {
   // Empty URLs should get a placeholder.
   // TODO(csharrison): See if we can DCHECK that the URL will be valid here.
-  std::u16string title = url.spec().empty()
-                             ? l10n_util::GetStringUTF16(IDS_TAB_LOADING_TITLE)
-                             : base::UTF8ToUTF16(url.spec());
-
+  std::u16string title =
+      url.spec().empty()
+          ? l10n_util::GetStringUTF16(IDS_TAB_LOADING_TITLE)
+          : url_formatter::FormatUrl(
+                url, url_formatter::kFormatUrlOmitUsernamePassword,
+                base::UnescapeRule::NONE, nullptr, nullptr, nullptr);
   // Format the title to include the unicode single dot bullet code-point
   // \u2022 and two spaces.
   title = l10n_util::GetStringFUTF16(IDS_LIST_BULLET, title);
diff --git a/chrome/browser/ui/content_settings/content_setting_bubble_model_unittest.cc b/chrome/browser/ui/content_settings/content_setting_bubble_model_unittest.cc
index 10f443bb..ae12051 100644
--- a/chrome/browser/ui/content_settings/content_setting_bubble_model_unittest.cc
+++ b/chrome/browser/ui/content_settings/content_setting_bubble_model_unittest.cc
@@ -1391,6 +1391,44 @@
   }
 }
 
+TEST_F(ContentSettingBubbleModelTest, PopupBubbleModelListItemsSpoofing) {
+  const GURL url("https://www.example.test/");
+  WebContentsTester::For(web_contents())->NavigateAndCommit(url);
+  blocked_content::PopupBlockerTabHelper::CreateForWebContents(web_contents());
+
+  // Malicious URL
+  GURL spoof_url("http://google.com:[email protected]");
+
+  BlockedWindowParams params(spoof_url, url::Origin(), nullptr,
+                             content::Referrer(), std::string(),
+                             WindowOpenDisposition::NEW_POPUP,
+                             blink::mojom::WindowFeatures(), false, true);
+
+  NavigateParams navigate_params =
+      params.CreateNavigateParams(process(), web_contents());
+  blocked_content::MaybeBlockPopup(
+      web_contents(), &url,
+      std::make_unique<ChromePopupNavigationDelegate>(
+          std::move(navigate_params)),
+      nullptr, params.features(),
+      HostContentSettingsMapFactory::GetForProfile(profile()));
+
+  std::unique_ptr<ContentSettingBubbleModel> content_setting_bubble_model(
+      ContentSettingBubbleModel::CreateContentSettingBubbleModel(
+          nullptr, web_contents(), ContentSettingsType::POPUPS));
+
+  const auto& list_items =
+      content_setting_bubble_model->bubble_content().list_items;
+  ASSERT_EQ(1U, list_items.size());
+
+  // Check the title
+  std::u16string title = list_items[0].title;
+  // It should NOT contain "google.com" if it's formatted correctly for
+  // security.
+  EXPECT_EQ(std::u16string::npos, title.find(u"google.com"));
+  EXPECT_NE(std::u16string::npos, title.find(u"evil.com"));
+}
+
 TEST_F(ContentSettingBubbleModelTest, ValidUrl) {
   WebContentsTester::For(web_contents())
       ->NavigateAndCommit(GURL("https://www.example.com"));
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/ui/content_settings/content_setting_bubble_model_unittest.cc b/chrome/browser/ui/content_settings/content_setting_bubble_model_unittest.cc
index 10f443bb..ae12051 100644
--- a/chrome/browser/ui/content_settings/content_setting_bubble_model_unittest.cc
+++ b/chrome/browser/ui/content_settings/content_setting_bubble_model_unittest.cc
@@ -1391,6 +1391,44 @@
   }
 }
 
+TEST_F(ContentSettingBubbleModelTest, PopupBubbleModelListItemsSpoofing) {
+  const GURL url("https://www.example.test/");
+  WebContentsTester::For(web_contents())->NavigateAndCommit(url);
+  blocked_content::PopupBlockerTabHelper::CreateForWebContents(web_contents());
+
+  // Malicious URL
+  GURL spoof_url("http://google.com:[email protected]");
+
+  BlockedWindowParams params(spoof_url, url::Origin(), nullptr,
+                             content::Referrer(), std::string(),
+                             WindowOpenDisposition::NEW_POPUP,
+                             blink::mojom::WindowFeatures(), false, true);
+
+  NavigateParams navigate_params =
+      params.CreateNavigateParams(process(), web_contents());
+  blocked_content::MaybeBlockPopup(
+      web_contents(), &url,
+      std::make_unique<ChromePopupNavigationDelegate>(
+          std::move(navigate_params)),
+      nullptr, params.features(),
+      HostContentSettingsMapFactory::GetForProfile(profile()));
+
+  std::unique_ptr<ContentSettingBubbleModel> content_setting_bubble_model(
+      ContentSettingBubbleModel::CreateContentSettingBubbleModel(
+          nullptr, web_contents(), ContentSettingsType::POPUPS));
+
+  const auto& list_items =
+      content_setting_bubble_model->bubble_content().list_items;
+  ASSERT_EQ(1U, list_items.size());
+
+  // Check the title
+  std::u16string title = list_items[0].title;
+  // It should NOT contain "google.com" if it's formatted correctly for
+  // security.
+  EXPECT_EQ(std::u16string::npos, title.find(u"google.com"));
+  EXPECT_NE(std::u16string::npos, title.find(u"evil.com"));
+}
+
 TEST_F(ContentSettingBubbleModelTest, ValidUrl) {
   WebContentsTester::For(web_contents())
       ->NavigateAndCommit(GURL("https://www.example.com"));
Loading diff…

Original Bug Report

reported by [email protected]

URL Spoofing on Block or allow pop-ups in Chrome

Steps to reproduce the problem

  1. Save this 2 HTML files (orig.html and fake.html) and analyze the link in the popup message, it looks the same
  2. Allow the popup
  3. The victim redirected to evil.com instead of google.com

Problem Description

When using window.open, Chrome shows a popup listing temporarily blocked URLs and waits for the user’s approval. If a suspicious URL is detected, the user can choose to keep it blocked. However, in this popup Chrome displays only the URL’s prefix and suffix. By inserting a malicious URL in the middle using @, an attacker can disguise it. If the user allows the popup, instead of opening a safe website, Chrome will open the malicious URL.

Summary

URL Spoofing on Block or allow pop-ups in Chrome

Additional Data

Category: Security
Chrome Channel: Not sure
Regression: N/A \

View on issue tracker