Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in DevTools
DescriptionInappropriate implementation in DevTools
ComponentDevTools
Bug ClassLogic Error
Tracker439058242
Fix commit2f952b779648 (chromium/src) +3/-29
CISA KEVNot listed
CreditedLeandro Teles
Disclosed2025-12-02

Changed Functions

FunctionChangeNotes
if
chrome/browser/extensions/extension_tab_util.cc
modified

Files Changed

  • chrome/browser/extensions/extension_tab_util.cc
  • chrome/browser/extensions/extension_tab_util.h
  • chrome/browser/extensions/extension_tab_util_unittest.cc
From 2f952b7796483626f16d5995221b4dca8c3680c2 Mon Sep 17 00:00:00 2001
From: Devlin Cronin <[email protected]>
Date: Mon, 13 Oct 2025 14:12:00 -0700
Subject: [PATCH] [Extensions] Block devtools window / tab creation from extension APIs

We currently have a carve-out that allows extensions with the devtools
or debugger permissions to create a devtools window. Even with these
permissions, this is something we'd prefer be left up to the user to
initiate. Don't allow these navigations.

Bug: 439058242
Change-Id: I402d52ffbbd6f8537df92d12a3211ca70ff7ae3c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7030016
Commit-Queue: Devlin Cronin <[email protected]>
Reviewed-by: Oliver Dunk <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1529133}
---

diff --git a/chrome/browser/extensions/extension_tab_util.cc b/chrome/browser/extensions/extension_tab_util.cc
index 6d3c3a95..1173528 100644
--- a/chrome/browser/extensions/extension_tab_util.cc
+++ b/chrome/browser/extensions/extension_tab_util.cc
@@ -1341,17 +1341,9 @@
     return base::unexpected(kNoCrashBrowserError);
   }
 
-  // Don't let the extension navigate directly to devtools scheme pages, unless
-  // they have applicable permissions.
+  // Don't let the extension navigate directly to devtools scheme pages.
   if (url.SchemeIs(content::kChromeDevToolsScheme)) {
-    bool has_permission =
-        extension && (extension->permissions_data()->HasAPIPermission(
-                          APIPermissionID::kDevtools) ||
-                      extension->permissions_data()->HasAPIPermission(
-                          APIPermissionID::kDebugger));
-    if (!has_permission) {
-      return base::unexpected(kCannotNavigateToDevtools);
-    }
+    return base::unexpected(kCannotNavigateToDevtools);
   }
 
   // Don't let the extension navigate directly to chrome-untrusted scheme pages.
diff --git a/chrome/browser/extensions/extension_tab_util.h b/chrome/browser/extensions/extension_tab_util.h
index aa2d679f..fb5e272 100644
--- a/chrome/browser/extensions/extension_tab_util.h
+++ b/chrome/browser/extensions/extension_tab_util.h
@@ -82,8 +82,7 @@
       "Browser windows not allowed.";
 #endif  // !BUILDFLAG(IS_ANDROID)
   static constexpr char kCannotNavigateToDevtools[] =
-      "Cannot navigate to a devtools:// page without either the devtools or "
-      "debugger permission.";
+      "Cannot navigate to a devtools:// page.";
 #if !BUILDFLAG(IS_ANDROID)
   static constexpr char kLockedFullscreenModeNewTabError[] =
       "You cannot create new tabs while in locked fullscreen mode.";
diff --git a/chrome/browser/extensions/extension_tab_util_unittest.cc b/chrome/browser/extensions/extension_tab_util_unittest.cc
index 603c0655..5b290ed4 100644
--- a/chrome/browser/extensions/extension_tab_util_unittest.cc
+++ b/chrome/browser/extensions/extension_tab_util_unittest.cc
@@ -249,23 +249,6 @@
     EXPECT_THAT(
         url, base::test::ErrorIs(ExtensionTabUtil::kCannotNavigateToDevtools));
   }
-  // Having the devtools permissions should allow access.
-  {
-    auto devtools_extension = ExtensionBuilder("devtools")
-                                  .SetManifestKey("devtools_page", "foo.html")
-                                  .Build();
-    auto url = ExtensionTabUtil::PrepareURLForNavigation(
-        kDevtoolsURL, devtools_extension.get(), browser_context());
-    EXPECT_THAT(url, base::test::ValueIs(kDevtoolsURL));
-  }
-  // Having the debugger permissions should also allow access.
-  {
-    auto debugger_extension =
-        ExtensionBuilder("debugger").AddAPIPermission("debugger").Build();
-    auto url = ExtensionTabUtil::PrepareURLForNavigation(
-        kDevtoolsURL, debugger_extension.get(), browser_context());
-    EXPECT_THAT(url, base::test::ValueIs(kDevtoolsURL));
-  }
 }
 
 TEST_F(ChromeExtensionNavigationTest,
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/extensions/extension_tab_util_unittest.cc b/chrome/browser/extensions/extension_tab_util_unittest.cc
index 603c0655..5b290ed4 100644
--- a/chrome/browser/extensions/extension_tab_util_unittest.cc
+++ b/chrome/browser/extensions/extension_tab_util_unittest.cc
@@ -249,23 +249,6 @@
     EXPECT_THAT(
         url, base::test::ErrorIs(ExtensionTabUtil::kCannotNavigateToDevtools));
   }
-  // Having the devtools permissions should allow access.
-  {
-    auto devtools_extension = ExtensionBuilder("devtools")
-                                  .SetManifestKey("devtools_page", "foo.html")
-                                  .Build();
-    auto url = ExtensionTabUtil::PrepareURLForNavigation(
-        kDevtoolsURL, devtools_extension.get(), browser_context());
-    EXPECT_THAT(url, base::test::ValueIs(kDevtoolsURL));
-  }
-  // Having the debugger permissions should also allow access.
-  {
-    auto debugger_extension =
-        ExtensionBuilder("debugger").AddAPIPermission("debugger").Build();
-    auto url = ExtensionTabUtil::PrepareURLForNavigation(
-        kDevtoolsURL, debugger_extension.get(), browser_context());
-    EXPECT_THAT(url, base::test::ValueIs(kDevtoolsURL));
-  }
 }
 
 TEST_F(ChromeExtensionNavigationTest,
Loading diff…

Original Bug Report

reported by [email protected]

Extensions can run JS on any privileged origin by exploiting already-patched vulnerabilities under devtools:// scheme.

VULNERABILITY DETAILS

The devtools:// scheme allows loading of remote assets from the domain https://chrome-devtools-frontend.appspot.com/. This server keeps a history of old versions of the DevTools Frontend, which can be loaded by navigating any tab to the URL “devtools://devtools/remote/serve_file/@<commit_hash>/<filename>”.

When loaded this way, these assets are fully-privileged, with access to the DevToolsAPI object. Because these are old versions, they contain many vulnerabilities that have already been patched, effectively allowing the exploitation of such vulnerabilities even in the latest Chrome version.

EXPLANATION

An extension can open a new tab and browse to a devtools:// URL that loads an old version of the DevTools frontend from the remote server, then exploit any old vulnerability to achieve code execution under the DevTools page. This allows a malicious extension to obtain access to the DevToolsAPI object, which can do many privileged operations, such as accessing local files, impersonating other extensions (even privileged ones), and performing universal XSS. All of this can be done with no user interaction, other than just installing the malicious extension.

In addition to that, the extension can also setup “Console Pins”, by writing to localStorage when running code under the old DevTools Frontend. These Console Pins will then execute in ANY privileged origin, allowing a full sandbox escape, as soon as the user opens the real DevTools console. It’s not needed to open the console in a privileged page, just by opening console anywhere, the extension can navigate the inspected tab to a privileged page, making the Console Pin execute there.

Another way to escape sandbox, is by using DevTools to impersonate a highly-privileged extension such as Perfetto UI, which can control the entire browser (its id is hardcoded in chromium sources). This is demonstrated in the attached “PoC 2”. If Perfetto UI is already installed, no user interaction is required, if not, we use the webstorePrivate API to show an extension install prompt (we can even change name and icon, to make it look like our own extension). If the user accepts the prompt, Perfetto UI will be installed, and we will impersonate it.

VERSION

Chrome Version: 139.0.7258.128 stable

Operating System: Tested on Windows 11, should work on all operating systems.

REPRODUCTION CASE

In this PoC, we are reusing the vulnerability described in https://issues.chromium.org/issues/40051844. This vulnerability has been patched long ago, but because we are loading an old DevTools frontend, we can reuse it.

Steps to reproduce:

  1. Download the attached “PoC 1.zip” file and install it as an unpacked extension
  2. As soon as the extension is installed, it will open a new window with the url devtools://devtools/remote/serve_file/@e22de67c28798d98833a7137c0e22876237fc40a/inspector.html.
  3. After a few seconds, the extension will write the contents of the C:\ directory to the body of the DevTools page, demonstrating that it achieved local file access through the DevToolsAPI object. No user interaction is required up to this step.
  4. The extension will also setup a Console Pin, and wait for DevTools to be opened. As soon as you press F12 in any page, that page will be navigated to chrome://inspect, and the Console Pin will execute there.
  5. The script injected in chrome://inspect will open the Node Frontend, then it will open a DevTools console to inspect the Node Frontend. This will cause the Console Pin to be injected in the Node Frontend too.
  6. Since the Node Frontend is attached to the browser target, the injected script has full control over the browser. The script will download calc.exe and use the chrome://downloads page to execute it.
  7. At the end of the process, the extension will remove the console pin and uninstall itself, to avoid an infinite loop, and to not interefere with PoC #2.

The second PoC demonstrates the usage of the webstorePrivate API, to spoof a WebStore prompt, tricking the user into installing the Perfetto UI extension, despite the name and icon in the prompt being completely different. This uses the same vulnerability as above, but it is much stealthier: No UI is displayed to the user, other than a minimized DevTools window that exists briefly when the extension is loaded. It also performs a full sandbox escape, downloading and executing calc.exe in the end.

Steps to reproduce:

  1. Download and install “PoC 2.zip” as an unpacked extension.
  2. A minimized, barely perceptible, DevTools window, will appear in the taskbar for some seconds, and then close itself.
  3. After that, open a new tab and paste an URL of the Chrome Web Store, for ANY extension (ex. https://chromewebstore.google.com/detail/framesexplorer/imijdbpfemdegalijeojlkhiamfcgklp). After the page is fully loaded, click on the install button, and accept the prompt.
  4. As soon as the installation of your chosen extension is finished, you will see an alert in the WebStore page saying “Failed to install <extension>, please try again”. The alert shows “Web Store extension” as the origin.
  5. After accepting the alert, you will see a second install prompt, that looks exactly like the prompt you just accepted (same name and icon). This would lead a user to believe that the installation “failed”, and the browser is “trying again”. But in reality, this second prompt, if accepted, will install the Perfetto UI extension.
  6. As soon as Perfetto UI is installed, the extension will open another minimized DevTools window, to impersonate the Perfetto UI extension, and take control of the browser. After a few seconds, calc.exe will be downloaded and launched.

CREDIT INFORMATION

Reporter credit: Leandro Teles (https://x.com/leandrotp2, https://www.linkedin.com/in/leandro-teles-212014197/)

View on issue tracker