Medium chrome Input Validation 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper input validation in Extensions
DescriptionImproper input validation in Extensions
ComponentExtensions
Sub-componentPlatform>Extensions
Bug ClassInput Validation
Tracker477664550
Fix commit137aa13aa45c (chromium/src) +306/-4
CISA KEVNot listed
CreditedLuan Herrera (@lbherrera_)
Disclosed2026-10-20

Files Changed

  • chrome/browser/extensions/permissions/active_tab_unittest.cc
  • chrome/common/extensions/chrome_extensions_client.cc
  • chrome/browser/extensions/api/tab_capture/tab_capture_registry.cc
  • extensions/common/permissions/permissions_data.cc
  • chrome/common/extensions/BUILD.gn
  • extensions/common/extensions_client.h
  • chrome/browser/extensions/extension_tab_util.cc
  • chrome/browser/extensions/extension_tab_util_unittest.cc
  • chrome/common/extensions/DEPS
  • extensions/common/extensions_client.cc
  • chrome/browser/extensions/extension_tab_util.h
  • chrome/common/extensions/chrome_extensions_client.h
  • chrome/browser/extensions/BUILD.gn
  • chrome/browser/extensions/api/tabs/tabs_apitest.cc

Original Bug Report

reported by he...@gmail.com

Extensions can read arbitrary local files without access to file URLs

VULNERABILITY DETAILS

It was discovered that a Chrome extension that is granted the permissions activeTab, pageCapture, and either declarativeNetRequest or downloads can read arbitrary local files and capture full desktop screenshots without requiring local file access.

This issue is a variant of the local file read issue described in https://issues.chromium.org/issues/40061772. But instead of directly attempting to capture file:// URLs via activeTab, this attack chain uses chrome://feedback as an intermediary.

The chrome://feedback page automatically captures a screenshot of the user’s entire desktop and embeds it into the DOM, which the extension can then exfiltrate.

At a high level, the attack leverages the following behaviors:

  1. When an extension keyboard shortcut is triggered, the activeTab permission can be granted even if the corresponding window is off-screen (issue 477327257). Alternatively, by foregrounding a window at the same moment the user completes the shortcut, the same result can be achieved.
  2. Extensions are allowed to open chrome:// URIs, and activeTab can be granted to them.
  3. The chrome://feedback page automatically captures the user’s entire desktop and embeds it into the DOM.
  4. The pageCapture.saveAsMHTML() function serializes the full page, including embedded blob: resources.
  5. Embedded desktop screenshots can be extracted from the resulting MHTML archive, yielding a full desktop capture without the appropriate permissions.
  6. An extension can open arbitrary file:// URLs without file access permissions using either declarativeNetRequest or the downloads / downloads.open APIs.

Obtaining activeTab on a chrome://feedback page

To capture internal Chrome pages, the extension must cause chrome://feedback to be the active tab at the moment an extension command is triggered, thereby granting the activeTab permission to it.

I identified two methods to accomplish this.

Method A: Off-screen window bypass (issue 477327257)

The PoC uses an off-screen window bypass to load chrome://feedback outside of the visible display area. Due to integer division in WindowBoundsIntersectDisplays, a window can be moved completely outside of all displays while still being considered visible.

This allows chrome://feedback to load and remain active without user awareness. When the extension command is triggered, activeTab is granted to this hidden window.

Method B: Timed windows focus

Even without relying on the off-screen window issue, the same outcome can be achieved via precise timing.

  1. The extension opens chrome://feedback as a minimized window.
  2. The extension listens for partial key presses (e.g. Ctrl+Shift).
  3. When detected, it brings the chrome://feedback window to the foreground.
  4. When the user completes the shortcut (e.g. presses S), activeTab is granted to chrome://feedback.

The activeTab permission allows capture of chrome:// pages

Once chrome://feedback becomes the active tab, the activeTab permission allows access to Chrome internal pages by design.

The CanCaptureVisiblePage logic explicitly permits capture of chrome:// origins when activeTab is present.

// extensions/common/permissions/permissions_data.cc
bool allowed_with_active_tab =
    origin_url.SchemeIs(content::kChromeUIScheme) ||
    origin_url.SchemeIs(kExtensionScheme) ||
    document_url.SchemeIs(url::kDataScheme) ||
 extension_urls::IsWebstoreOrigin(origin);

if (!allowed_with_active_tab) {
  if (error)
    *error = access_error;
  return false;
}

if (has_active_tab)
  return true;

As a result, once either of the methods above succeeds in activating chrome://feedback, the extension can invoke APIs such as pageCapture against it.

The chrome://feedback embeds a full desktop screenshot

The chrome://feedback page automatically captures the user’s entire desktop using getUserMedia with chromeMediaSource: "screen".

// chrome/browser/resources/feedback/js/take_screenshot.ts
FeedbackBrowserProxyImpl.getInstance().getUserMedia({
    video: {
        mandatory: {
            chromeMediaSource: 'screen',
            maxWidth: 4096,
            maxHeight: 2560
        }
    }
});

The resulting screenshot is rendered into the DOM as an <img> element backed by a blob: URL.

Extracting the desktop screenshot via pageCapture

With activeTab on chrome://feedback, the extension can call the following function.

// background.js
chrome.pageCapture.saveAsMHTML({ tabId: feedbackTabId }, async (mhtmlData) => {
  const text = await mhtmlData.text();
  const regex =
    /Content-Type:\s*image\/png[\s\S]*?Content-Location:\s*blob:chrome:\/\/feedback\/[^\r\n]+\r?\n\r?\n([\s\S]*?)(?=\r?\n------MultipartBoundary)/;

  const match = text.match(regex);
  if (match && match[1]) {
    const base64Data = match[1].replace(/[\r\n\s]/g, '');
    const screenshotUrl = 'data:image/png;base64,' + base64Data;
    // Full desktop screenshot extracted
 }
});

The MHTML archive contains all embedded resources, including the desktop screenshot blob, allowing the extension to fully reconstruct the image.

Opening arbitrary local files

In addition to the screenshot issue, the attack requires a local file to be opened at the moment the chrome://feedback page is reloaded. This ensures that the desktop screenshot captures the contents of the targeted local file.

I also identified two ways to achieve this.

Method A: declarativeNetRequest redirect to file://

The declarativeNetRequest API validates redirect URLs but only explicitly blocks the javascript: scheme.

// extensions/browser/api/declarative_net_request/indexed_rule.cc
if (redirect.url) {
 GURL redirect_url = GURL(*redirect.url);
  if (!redirect_url.is_valid()) {
    return ParseResult::ERROR_INVALID_REDIRECT_URL;
  }

  if (redirect_url.SchemeIs(url::kJavaScriptScheme)) {
    return ParseResult::ERROR_JAVASCRIPT_REDIRECT;
  }

  indexed_rule->redirect_url = std::move(*redirect.url);
  return ParseResult::SUCCESS;
}

As a result, redirect rules may point to file:// URLs and cause local files to be rendered inside tabs.

Method B: Downloading and opening an HTML file via downloads.open

The same result can be achieved without the declarativeNetRequest permission. By using the downloads permission, an HTML file containing the following HTML can be automatically downloaded.

<script>
  location = "file:///C:/target/file.txt";
</script>

The extension can then call chrome.downloads.open() on the downloaded file. When opened, the HTML file automatically redirects the tab to the targeted local file, which is then rendered by the browser.

BISECT

The initial bisect indicated that the regression was introduced after revision 885350 and no later than 885361 (https://chromium.googlesource.com/chromium/src/+log/0cbccf3b2fa2681d62137763e99c3e7414a948d9..63d951cf4f679c28468c5876f8ac708d03cfbeb5).

Upon reviewing the changes, it became clear that the relevant commit was https://chromium.googlesource.com/chromium/src/+/99860a6c18a362d054b2950af11cd393ee8f71a1, which enabled a field trial for the WebUI Feedback component. I then reran the bisect with the corresponding field trial explicitly enabled.

The new bisect narrowed the range to the following set of changes: https://chromium.googlesource.com/chromium/src/+log/0e93354c6108ed5d8e54e9ade93a06932dd7871a..ec84e11c2dfa4ba8443aa3021e3c8bdce3c2d50e.

Further investigation showed that the underlying behavior was introduced by https://chromium.googlesource.com/chromium/src/+/3ea5768ca199f824699e4b9860fac15d1d613851.

It is worth noting that this bisect was performed using a reduced test case that relies only on the timing and downloads permission method, given that they do not depend on any additional vulnerabilities that may have been introduced later.

This allowed me to identify the earliest point at which the attack chain became exploitable.

VERSION

Chrome Version: 144.0.7559.9
Chrome Version: 145.0.7632.5
Chrome Version: 146.0.7635.3
Chrome Version: 146.0.7646.0
Operating System: Windows 11 24H2

REPRODUCTION CASE

  1. Download manifest.json, background.js and rules.json.
  2. Place those files in the same directory and load the extension into Chrome.
  3. Press Ctrl+Shift+S.
  4. A tab is automatically opened containing an image of the user’s desktop, including the contents of the targeted local file. Note that the image is displayed only for proof-of-concept purposes. A malicious extension could silently exfiltrate the data without any user-visible indication.

I have also attached a video reproducing the attack (repro.mp4).

CREDIT INFORMATION

Reporter credit: Luan Herrera (@lbherrera_)

View on issue tracker