Chrome · Network
CVE-2026-79253
Logic Error in Network
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Files Changed
chrome/browser/download/download_target_determiner_unittest.cccontent/browser/file_system_access/file_system_chooser.cccontent/browser/web_contents/web_contents_view_aura_unittest.ccnet/base/filename_util_internal.ccnet/base/filename_util_unittest.cc
Patch
From a94fdc22a1e11467b08cbc4bc5b8e062ae8bc358 Mon Sep 17 00:00:00 2001 From: Etienne Bergeron <[email protected]> Date: Fri, 17 Jul 2026 07:38:20 -0700 Subject: [PATCH] [net/base] Block .scf and .url files in IsShellIntegratedExtension A divergence between net::IsShellIntegratedExtension() and FileSystemChooser::IsShellIntegratedExtension() allowed .url and .scf files to bypass extension sanitization during standard downloads. Windows Explorer automatically parses .url and .scf files upon folder view (without the user double-clicking them) to retrieve metadata or render icons. If a downloaded .url contains an IconFile pointing to a remote UNC path, simply viewing the Downloads directory triggers an automatic outbound SMB connection, leading to a disclosure of the victim's NetNTLMv2 credential hash. This CL adds "scf" and "url" to net::IsShellIntegratedExtension(), ensuring they are correctly renamed to ".download" on Windows during standard downloads. Bug: 533511921 Test: net_unittests --gtest_filter=FilenameUtilTest.* Change-Id: Iff138ff80003999fd3f447dffec1647d45ab2b1d Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8086934 Reviewed-by: Kenichi Ishibashi <[email protected]> Reviewed-by: Arthur Sonzogni <[email protected]> Commit-Queue: Etienne Bergeron <[email protected]> Cr-Commit-Position: refs/heads/main@{#1663888} --- diff --git a/chrome/browser/download/download_target_determiner_unittest.cc b/chrome/browser/download/download_target_determiner_unittest.cc index 07a025c..b113eb7 100644 --- a/chrome/browser/download/download_target_determiner_unittest.cc +++ b/chrome/browser/download/download_target_determiner_unittest.cc @@ -2839,7 +2839,8 @@ DownloadItem::TARGET_DISPOSITION_PROMPT, EXPECT_CRDOWNLOAD}, - {// 2: File name falling back to dangerous extensions after removing env var. + {// 2: File name falling back to dangerous extensions after removing env + // var. SAVE_AS, download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, DownloadFileType::NOT_DANGEROUS, "http://example.com/foo2.lnk.%%", "application/octet-stream", FILE_PATH_LITERAL(""), @@ -2848,7 +2849,8 @@ DownloadItem::TARGET_DISPOSITION_PROMPT, EXPECT_CRDOWNLOAD}, - {// 3: Double extension bug leading to dangerous extensions after removing env var. + {// 3: Double extension bug leading to dangerous extensions after removing + // env var. SAVE_AS, download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, DownloadFileType::NOT_DANGEROUS, "http://example.com/foo2.lnk %%", "application/octet-stream", FILE_PATH_LITERAL(""), @@ -2857,8 +2859,9 @@ DownloadItem::TARGET_DISPOSITION_PROMPT, EXPECT_CRDOWNLOAD}, - {// 4: Unicode char bug leading to dangerous extensions after removing env var. - // NOTE: The space before "%%" is a non-breaking space (U+00A0), not a normal space. + {// 4: Unicode char bug leading to dangerous extensions after removing env + // var. NOTE: The space before "%%" is a non-breaking space (U+00A0), not + // a normal space. SAVE_AS, download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, DownloadFileType::NOT_DANGEROUS, "http://example.com/foo2.lnk %%", "application/octet-stream", FILE_PATH_LITERAL(""), @@ -2891,7 +2894,7 @@ "http://example.com/photo.jpg%20%25%25.url", "text/plain", FILE_PATH_LITERAL(""), - FILE_PATH_LITERAL("photo.jpg.url"), + FILE_PATH_LITERAL("photo.jpg.download"), DownloadItem::TARGET_DISPOSITION_PROMPT, EXPECT_CRDOWNLOAD}, diff --git a/content/browser/file_system_access/file_system_chooser.cc b/content/browser/file_system_access/file_system_chooser.cc index 897ae7f..d2bf686 100644 --- a/content/browser/file_system_access/file_system_chooser.cc +++ b/content/browser/file_system_access/file_system_chooser.cc @@ -388,12 +388,14 @@ // https://crbug.com/1227995, respectively). '.local' files are used by // Windows to determine which DLLs to load for an application. '.url' files // can be used to read arbirtary files (see https://crbug.com/1307930). + // LINT.IfChange(ShellIntegratedExtensions) if ((extension_lower == FILE_PATH_LITERAL("lnk")) || (extension_lower == FILE_PATH_LITERAL("local")) || (extension_lower == FILE_PATH_LITERAL("scf")) || (extension_lower == FILE_PATH_LITERAL("url"))) { return true; } + // LINT.ThenChange(//net/base/filename_util_internal.cc:ShellIntegratedExtensions) // Setting a file's extension to a CLSID may conceal its actual file type on // some Windows versions (see https://nvd.nist.gov/vuln/detail/CVE-2004-0420). diff --git a/content/browser/web_contents/web_contents_view_aura_unittest.cc b/content/browser/web_contents/web_contents_view_aura_unittest.cc index 5eb6c0c..ce85bc8d 100644 --- a/content/browser/web_contents/web_contents_view_aura_unittest.cc +++ b/content/browser/web_contents/web_contents_view_aura_unittest.cc @@ -711,7 +711,7 @@ data->GetVirtualFilenames(); ASSERT_TRUE(file_infos.has_value()); ASSERT_EQ(1ULL, file_infos.value().size()); - EXPECT_EQ(base::FilePath(base::UTF16ToWide(url_title) + L".url"), + EXPECT_EQ(base::FilePath(base::UTF16ToWide(url_title) + L".download"), file_infos.value()[0].display_name); ui::DropTargetEvent event(*data.get(), kClientPt, kScreenPt, diff --git a/net/base/filename_util_internal.cc b/net/base/filename_util_internal.cc index d5799604..1d3c9e6 100644 --- a/net/base/filename_util_internal.cc +++ b/net/base/filename_util_internal.cc @@ -155,12 +155,20 @@ bool IsShellIntegratedExtension(const base::FilePath::StringType& extension) { base::FilePath::StringType extension_lower = base::ToLowerASCII(extension); - // .lnk files may be used to execute arbitrary code (see - // https://nvd.nist.gov/vuln/detail/CVE-2010-2568). .local files are used by - // Windows to determine which DLLs to load for an application. + // .lnk and .scf files may be used to execute arbitrary code (see + // https://nvd.nist.gov/vuln/detail/CVE-2010-2568 and + // https://crbug.com/1227995, respectively). .local files are used by + // Windows to determine which DLLs to load for an application. .url files + // can be used to leak credentials or read arbitrary files (see + // https://crbug.com/1307930). + // LINT.IfChange(ShellIntegratedExtensions) if ((extension_lower == FILE_PATH_LITERAL("local")) || - (extension_lower == FILE_PATH_LITERAL("lnk"))) + (extension_lower == FILE_PATH_LITERAL("lnk")) || + (extension_lower == FILE_PATH_LITERAL("scf")) || + (extension_lower == FILE_PATH_LITERAL("url"))) { return true; + } + // LINT.ThenChange(//content/browser/file_system_access/file_system_chooser.cc:ShellIntegratedExtensions) // Setting a file's extension to a CLSID may conceal its actual file type on // some Windows versions (see https://nvd.nist.gov/vuln/detail/CVE-2004-0420). diff --git a/net/base/filename_util_unittest.cc b/net/base/filename_util_unittest.cc index ca470af..6f06b6c0 100644 --- a/net/base/filename_util_unittest.cc +++ b/net/base/filename_util_unittest.cc @@ -109,6 +109,8 @@ FILE_PATH_LITERAL(" Computer"), FILE_PATH_LITERAL("My Computer.{a}"), FILE_PATH_LITERAL("My Computer.{20D04FE0-3AEA-1069-A2D8-08002B30309D}"), + FILE_PATH_LITERAL("harmless.scf"), + FILE_PATH_LITERAL("harmless.url"), #if BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA) FILE_PATH_LITERAL("a\\a"), #endif @@ -355,41 +357,45 @@ const char* filename; const char* expected_filename; } safe_tests[] = { - {__LINE__, "text/html", "bar.htm", "bar.htm"}, - {__LINE__, "text/html", "bar.html", "bar.html"}, - {__LINE__, "application/x-chrome-extension", "bar", "bar.crx"}, - {__LINE__, "image/png", "bar.html", "bar.html"}, - {__LINE__, "text/html", "bar.exe", "bar.exe"}, - {__LINE__, "image/gif", "bar.exe", "bar.exe"}, - {__LINE__, "text/html", "google.com", "google.com"}, - // Allow extension synonyms. - {__LINE__, "image/jpeg", "bar.jpg", "bar.jpg"}, - {__LINE__, "image/jpeg", "bar.jpeg", "bar.jpeg"}, + {__LINE__, "text/html", "bar.htm", "bar.htm"}, + {__LINE__, "text/html", "bar.html", "bar.html"}, + {__LINE__, "application/x-chrome-extension", "bar", "bar.crx"}, + {__LINE__, "image/png", "bar.html", "bar.html"}, + {__LINE__, "text/html", "bar.exe", "bar.exe"}, + {__LINE__, "image/gif", "bar.exe", "bar.exe"}, + {__LINE__, "text/html", "google.com", "google.com"}, + // Allow extension synonyms. + {__LINE__, "image/jpeg", "bar.jpg", "bar.jpg"}, + {__LINE__, "image/jpeg", "bar.jpeg", "bar.jpeg"}, #if BUILDFLAG(IS_WIN) - // Device names - {__LINE__, "text/html", "con.htm", "_con.htm"}, - {__LINE__, "text/html", "lpt1.htm", "_lpt1.htm"}, - {__LINE__, "application/x-chrome-extension", "con", "_con.crx"}, + // Device names + {__LINE__, "text/html", "con.htm", "_con.htm"}, + {__LINE__, "text/html", "lpt1.htm", "_lpt1.htm"}, + {__LINE__, "application/x-chrome-extension", "con", "_con.crx"}, - // Looks like foo.{GUID} which get treated as namespace mounts on Windows. - {__LINE__, "text/html", "harmless.{not-really-this-may-be-a-guid}", - "harmless.download"}, - {__LINE__, "text/html", "harmless.{mismatched-", "harmless.{mismatched-"}, + // Looks like foo.{GUID} which get treated as namespace mounts on Windows. + {__LINE__, "text/html", "harmless.{not-really-this-may-be-a-guid}", + "harmless.download"}, + {__LINE__, "text/html", "harmless.{mismatched-", "harmless.{mismatched-"}, - // Dangerous extensions - {__LINE__, "text/html", "harmless.local", "harmless.download"}, - {__LINE__, "text/html", "harmless.lnk", "harmless.download"}, + // Dangerous extensions + {__LINE__, "text/html", "harmless.local", "harmless.download"}, + {__LINE__, "text/html", "harmless.lnk", "harmless.download"}, + {__LINE__, "text/html", "harmless.scf", "harmless.download"},
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/chrome/browser/download/download_target_determiner_unittest.cc b/chrome/browser/download/download_target_determiner_unittest.cc
index 07a025c..b113eb7 100644
--- a/chrome/browser/download/download_target_determiner_unittest.cc
+++ b/chrome/browser/download/download_target_determiner_unittest.cc
@@ -2839,7 +2839,8 @@
DownloadItem::TARGET_DISPOSITION_PROMPT,
EXPECT_CRDOWNLOAD},
- {// 2: File name falling back to dangerous extensions after removing env var.
+ {// 2: File name falling back to dangerous extensions after removing env
+ // var.
SAVE_AS, download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
DownloadFileType::NOT_DANGEROUS, "http://example.com/foo2.lnk.%%",
"application/octet-stream", FILE_PATH_LITERAL(""),
@@ -2848,7 +2849,8 @@
DownloadItem::TARGET_DISPOSITION_PROMPT,
EXPECT_CRDOWNLOAD},
- {// 3: Double extension bug leading to dangerous extensions after removing env var.
+ {// 3: Double extension bug leading to dangerous extensions after removing
+ // env var.
SAVE_AS, download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
DownloadFileType::NOT_DANGEROUS, "http://example.com/foo2.lnk %%",
"application/octet-stream", FILE_PATH_LITERAL(""),
@@ -2857,8 +2859,9 @@
DownloadItem::TARGET_DISPOSITION_PROMPT,
EXPECT_CRDOWNLOAD},
- {// 4: Unicode char bug leading to dangerous extensions after removing env var.
- // NOTE: The space before "%%" is a non-breaking space (U+00A0), not a normal space.
+ {// 4: Unicode char bug leading to dangerous extensions after removing env
+ // var. NOTE: The space before "%%" is a non-breaking space (U+00A0), not
+ // a normal space.
SAVE_AS, download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
DownloadFileType::NOT_DANGEROUS, "http://example.com/foo2.lnk %%",
"application/octet-stream", FILE_PATH_LITERAL(""),
@@ -2891,7 +2894,7 @@
"http://example.com/photo.jpg%20%25%25.url", "text/plain",
FILE_PATH_LITERAL(""),
- FILE_PATH_LITERAL("photo.jpg.url"),
+ FILE_PATH_LITERAL("photo.jpg.download"),
DownloadItem::TARGET_DISPOSITION_PROMPT,
EXPECT_CRDOWNLOAD},
diff --git a/content/browser/web_contents/web_contents_view_aura_unittest.cc b/content/browser/web_contents/web_contents_view_aura_unittest.cc
index 5eb6c0c..ce85bc8d 100644
--- a/content/browser/web_contents/web_contents_view_aura_unittest.cc
+++ b/content/browser/web_contents/web_contents_view_aura_unittest.cc
@@ -711,7 +711,7 @@
data->GetVirtualFilenames();
ASSERT_TRUE(file_infos.has_value());
ASSERT_EQ(1ULL, file_infos.value().size());
- EXPECT_EQ(base::FilePath(base::UTF16ToWide(url_title) + L".url"),
+ EXPECT_EQ(base::FilePath(base::UTF16ToWide(url_title) + L".download"),
file_infos.value()[0].display_name);
ui::DropTargetEvent event(*data.get(), kClientPt, kScreenPt,
diff --git a/net/base/filename_util_unittest.cc b/net/base/filename_util_unittest.cc
index ca470af..6f06b6c0 100644
--- a/net/base/filename_util_unittest.cc
+++ b/net/base/filename_util_unittest.cc
@@ -109,6 +109,8 @@
FILE_PATH_LITERAL(" Computer"),
FILE_PATH_LITERAL("My Computer.{a}"),
FILE_PATH_LITERAL("My Computer.{20D04FE0-3AEA-1069-A2D8-08002B30309D}"),
+ FILE_PATH_LITERAL("harmless.scf"),
+ FILE_PATH_LITERAL("harmless.url"),
#if BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
FILE_PATH_LITERAL("a\\a"),
#endif
@@ -355,41 +357,45 @@
const char* filename;
const char* expected_filename;
} safe_tests[] = {
- {__LINE__, "text/html", "bar.htm", "bar.htm"},
- {__LINE__, "text/html", "bar.html", "bar.html"},
- {__LINE__, "application/x-chrome-extension", "bar", "bar.crx"},
- {__LINE__, "image/png", "bar.html", "bar.html"},
- {__LINE__, "text/html", "bar.exe", "bar.exe"},
- {__LINE__, "image/gif", "bar.exe", "bar.exe"},
- {__LINE__, "text/html", "google.com", "google.com"},
- // Allow extension synonyms.
- {__LINE__, "image/jpeg", "bar.jpg", "bar.jpg"},
- {__LINE__, "image/jpeg", "bar.jpeg", "bar.jpeg"},
+ {__LINE__, "text/html", "bar.htm", "bar.htm"},
+ {__LINE__, "text/html", "bar.html", "bar.html"},
+ {__LINE__, "application/x-chrome-extension", "bar", "bar.crx"},
+ {__LINE__, "image/png", "bar.html", "bar.html"},
+ {__LINE__, "text/html", "bar.exe", "bar.exe"},
+ {__LINE__, "image/gif", "bar.exe", "bar.exe"},
+ {__LINE__, "text/html", "google.com", "google.com"},
+ // Allow extension synonyms.
+ {__LINE__, "image/jpeg", "bar.jpg", "bar.jpg"},
+ {__LINE__, "image/jpeg", "bar.jpeg", "bar.jpeg"},
#if BUILDFLAG(IS_WIN)
- // Device names
- {__LINE__, "text/html", "con.htm", "_con.htm"},
- {__LINE__, "text/html", "lpt1.htm", "_lpt1.htm"},
- {__LINE__, "application/x-chrome-extension", "con", "_con.crx"},
+ // Device names
+ {__LINE__, "text/html", "con.htm", "_con.htm"},
+ {__LINE__, "text/html", "lpt1.htm", "_lpt1.htm"},
+ {__LINE__, "application/x-chrome-extension", "con", "_con.crx"},
- // Looks like foo.{GUID} which get treated as namespace mounts on Windows.
- {__LINE__, "text/html", "harmless.{not-really-this-may-be-a-guid}",
- "harmless.download"},
- {__LINE__, "text/html", "harmless.{mismatched-", "harmless.{mismatched-"},
+ // Looks like foo.{GUID} which get treated as namespace mounts on Windows.
+ {__LINE__, "text/html", "harmless.{not-really-this-may-be-a-guid}",
+ "harmless.download"},
+ {__LINE__, "text/html", "harmless.{mismatched-", "harmless.{mismatched-"},
- // Dangerous extensions
- {__LINE__, "text/html", "harmless.local", "harmless.download"},
- {__LINE__, "text/html", "harmless.lnk", "harmless.download"},
+ // Dangerous extensions
+ {__LINE__, "text/html", "harmless.local", "harmless.download"},
+ {__LINE__, "text/html", "harmless.lnk", "harmless.download"},
+ {__LINE__, "text/html", "harmless.scf", "harmless.download"},
+ {__LINE__, "text/html", "harmless.url", "harmless.download"},
#elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
- // On Posix, none of the above set is particularly dangerous.
- {__LINE__, "text/html", "con.htm", "con.htm"},
- {__LINE__, "text/html", "lpt1.htm", "lpt1.htm"},
- {__LINE__, "application/x-chrome-extension", "con", "con.crx"},
- {__LINE__, "text/html", "harmless.{not-really-this-may-be-a-guid}",
- "harmless.{not-really-this-may-be-a-guid}"},
- {__LINE__, "text/html", "harmless.{mismatched-", "harmless.{mismatched-"},
- {__LINE__, "text/html", "harmless.local", "harmless.local"},
- {__LINE__, "text/html", "harmless.lnk", "harmless.lnk"},
+ // On Posix, none of the above set is particularly dangerous.
+ {__LINE__, "text/html", "con.htm", "con.htm"},
+ {__LINE__, "text/html", "lpt1.htm", "lpt1.htm"},
+ {__LINE__, "application/x-chrome-extension", "con", "con.crx"},
+ {__LINE__, "text/html", "harmless.{not-really-this-may-be-a-guid}",
+ "harmless.{not-really-this-may-be-a-guid}"},
+ {__LINE__, "text/html", "harmless.{mismatched-", "harmless.{mismatched-"},
+ {__LINE__, "text/html", "harmless.local", "harmless.local"},
+ {__LINE__, "text/html", "harmless.lnk", "harmless.lnk"},
+ {__LINE__, "text/html", "harmless.scf", "harmless.scf"},
+ {__LINE__, "text/html", "harmless.url", "harmless.url"},
#endif // BUILDFLAG(IS_WIN)
};
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page