CVE-2026-13925
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcontent/browser/download/save_file_manager.cc |
modified |
Files Changed
content/browser/download/save_file.cccontent/browser/download/save_file_manager.cccontent/browser/download/save_file_manager.hcontent/browser/download/save_types.cccontent/browser/download/save_types.h
Patch
From 9c86d91a91f72145f4136079de16325a96cb1496 Mon Sep 17 00:00:00 2001
From: Min Qin <qinmin@chromium.org>
Date: Thu, 21 May 2026 12:02:16 -0700
Subject: [PATCH] Use the destination URL after redirection for quarantine
This CL fixes an issue that quarantine checks the initial URL, instead
of the destination URL when saving a page.
Bug: 511802911
Change-Id: I1a3deed127add6db81fe20882303954807575cab
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7855782
Reviewed-by: Shakti Sahu <shaktisahu@chromium.org>
Commit-Queue: Min Qin <qinmin@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1634445}
---
diff --git a/content/browser/download/save_file.cc b/content/browser/download/save_file.cc
index 89dfc43..191c9c8b 100644
--- a/content/browser/download/save_file.cc
+++ b/content/browser/download/save_file.cc
@@ -93,7 +93,7 @@
void SaveFile::RunQuarantineCallback() {
if (!info_->quarantine_callback.is_null()) {
- std::move(info_->quarantine_callback).Run();
+ std::move(info_->quarantine_callback).Run(info_->final_url);
}
}
diff --git a/content/browser/download/save_file_manager.cc b/content/browser/download/save_file_manager.cc
index 0b6f3ee2..9f642b46 100644
--- a/content/browser/download/save_file_manager.cc
+++ b/content/browser/download/save_file_manager.cc
@@ -70,7 +70,7 @@
const net::NetworkTrafficAnnotationTag& annotation_tag,
network::mojom::URLLoaderFactory* url_loader_factory,
SaveFileManager* save_file_manager,
- base::OnceClosure quarantine_callback,
+ base::OnceCallback<void(const GURL&)> quarantine_callback,
URLLoaderCompleteCallback on_complete_cb) {
return std::unique_ptr<SimpleURLLoaderHelper>(new SimpleURLLoaderHelper(
std::move(resource_request), save_item_id, save_package_id,
@@ -94,7 +94,7 @@
const net::NetworkTrafficAnnotationTag& annotation_tag,
network::mojom::URLLoaderFactory* url_loader_factory,
SaveFileManager* save_file_manager,
- base::OnceClosure quarantine_callback,
+ base::OnceCallback<void(const GURL&)> quarantine_callback,
URLLoaderCompleteCallback on_complete_cb)
: save_file_manager_(save_file_manager),
save_item_id_(save_item_id),
@@ -159,7 +159,7 @@
SaveItemId save_item_id_;
SavePackageId save_package_id_;
std::unique_ptr<network::SimpleURLLoader> url_loader_;
- base::OnceClosure quarantine_callback_;
+ base::OnceCallback<void(const GURL&)> quarantine_callback_;
URLLoaderCompleteCallback on_complete_cb_;
};
@@ -232,11 +232,10 @@
DCHECK(!packages_.contains(save_item_id));
packages_[save_item_id] = save_package;
- base::OnceClosure quarantine_callback = base::BindOnce(
+ base::OnceCallback<void(const GURL&)> quarantine_callback = base::BindOnce(
&SaveFileManager::QuarantineItem, this, save_item_id, save_package->id(),
- context->IsOffTheRecord() ? GURL() : url,
context->IsOffTheRecord() ? GURL() : referrer.url, client_guid,
- std::move(remote_quarantine));
+ std::move(remote_quarantine), context->IsOffTheRecord());
// Register a saving job.
if (save_source == SaveFileCreateInfo::SAVE_FILE_FROM_NET) {
@@ -394,16 +393,18 @@
void SaveFileManager::QuarantineItem(
SaveItemId save_item_id,
SavePackageId save_package_id,
- const GURL& url,
const GURL& referrer_url,
const std::string& client_guid,
- mojo::PendingRemote<quarantine::mojom::Quarantine> remote_quarantine) {
+ mojo::PendingRemote<quarantine::mojom::Quarantine> remote_quarantine,
+ bool is_off_the_record,
+ const GURL& url) {
DCHECK(download::GetDownloadTaskRunner()->RunsTasksInCurrentSequence());
SaveFile* save_file = LookupSaveFile(save_item_id);
CHECK(save_file);
save_file->AnnotateWithSourceInformation(
- client_guid, url, referrer_url, std::move(remote_quarantine),
+ client_guid, is_off_the_record ? GURL() : url, referrer_url,
+ std::move(remote_quarantine),
base::BindOnce(&SaveFileManager::OnQuarantineComplete, this, save_item_id,
save_package_id));
}
diff --git a/content/browser/download/save_file_manager.h b/content/browser/download/save_file_manager.h
index 5297a0c..a245f93 100644
--- a/content/browser/download/save_file_manager.h
+++ b/content/browser/download/save_file_manager.h
@@ -194,10 +194,11 @@
void QuarantineItem(
SaveItemId save_item_id,
SavePackageId save_package_id,
- const GURL& url,
const GURL& referrer_url,
const std::string& client_guid,
- mojo::PendingRemote<quarantine::mojom::Quarantine> remote_quarantine);
+ mojo::PendingRemote<quarantine::mojom::Quarantine> remote_quarantine,
+ bool is_off_the_record,
+ const GURL& url);
// Called on the download TaskRunner when file quarantine finishes on a
// SaveItem.
diff --git a/content/browser/download/save_types.cc b/content/browser/download/save_types.cc
index 1fdcb7a8..9aba511 100644
--- a/content/browser/download/save_types.cc
+++ b/content/browser/download/save_types.cc
@@ -17,6 +17,7 @@
SaveFileSource save_source)
: path(path),
url(url),
+ final_url(url),
save_item_id(save_item_id),
save_package_id(save_package_id),
render_process_id(render_process_id),
diff --git a/content/browser/download/save_types.h b/content/browser/download/save_types.h
index d1ce26c0..3dd7040 100644
--- a/content/browser/download/save_types.h
+++ b/content/browser/download/save_types.h
@@ -82,7 +82,7 @@
// Source type of saved file.
SaveFileSource save_source;
// Callback to run to quarantine the file;
- base::OnceClosure quarantine_callback;
+ base::OnceCallback<void(const GURL&)> quarantine_callback;
};
} // namespace content
Original Bug Report
Potential SmartScreen bypass via Intranet MOTW spoofing in SavePackage
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 without the Chrome Security team. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: The SavePackage feature incorrectly uses the initial pre-redirect URL when annotating downloaded subresources with Mark-of-the-Web (MOTW). An attacker can leverage an open redirect on an Intranet domain to serve a malicious archive, assigning it an Intranet MOTW (ZoneId=1). This can bypass Windows SmartScreen protections when the extracted payload is executed.
Affected files:
content/browser/download/save_file_manager.cccontent/browser/download/save_package.cc
Estimated timestamp from git blame: 2025-10-22
Summary
When a user saves a webpage using the “Webpage, Complete” feature, Chrome downloads the page’s subresources. If a subresource download involves an HTTP redirect, Chrome incorrectly annotates the downloaded file with Mark-of-the-Web (MOTW) information based on the initial pre-redirect URL rather than the final destination URL.
In enterprise environments where internal domains are mapped to the “Local Intranet” security zone (ZoneId=1), an attacker who can leverage an open redirect on a trusted domain can use this flaw to deliver malicious payloads that bypass Windows SmartScreen. By delivering the payload within a format considered NOT_DANGEROUS by Chrome’s Safe Browsing policies (e.g., a .zip archive), the attacker avoids Chrome’s defense-in-depth file renaming mitigations. When the user extracts the archive using Windows Explorer, the Intranet MOTW propagates to the extracted executable, allowing it to run without SmartScreen warnings.
Technical Details
- When
SavePackagediscovers a subresource, it initiates a save request viaSaveFileManager::SaveURL(content/browser/download/save_file_manager.cc). SaveURLconstructs aquarantine_callbackto be executed when the download finishes. This callback is created usingbase::BindOnceand captures the original pre-redirecturlby value (lines 235-239).- The network request is made. If the server responds with a redirect, the
SimpleURLLoaderfollows it to thefinal_url. - When the response starts,
SimpleURLLoaderHelper::OnResponseStartedis called. It parses theContent-Dispositionheader from the final response (e.g.,attachment; filename="payload.zip") and creates aSaveFileCreateInfoobject. It moves the existingquarantine_callbackinto this object without updating the bound URL. - On the UI thread,
SavePackage::GenerateFileNameis called. It usesnet::GenerateFileNameImpl, which prioritizes the filename from theContent-Dispositionheader. The file is namedpayload.zip. - Because
.zipis consideredNOT_DANGEROUSindownload_file_types.asciipb,ChromeDownloadManagerDelegate::SanitizeSavePackageResourceNamedoes not append a.downloadextension to the file. - Once the download completes,
SaveFinishedexecutes the boundquarantine_callback. - The callback invokes
SaveFileManager::QuarantineItem, which receives the original Intranet URL. This URL is eventually passed to Windows viaquarantine::InvokeAttachmentServices. - Windows writes the
:Zone.Identifierstream topayload.zip. Because the original URL is in the Local Intranet zone, Windows setsZoneId=1. - When the user extracts the ZIP using Windows Explorer, the
ZoneId=1MOTW is propagated to the extracted executable. Double-clicking the executable bypasses SmartScreen Application Reputation checks because the file is considered to have originated from a trusted Intranet source.
Potential Exploitation Steps
Please note our tooling agent cannot execute code; these are suggested steps to verify the vulnerability.
- On a Windows machine, add a domain (e.g.,
internal.example.com) to the “Local Intranet” zone via Internet Options (Security tab). - Prepare a malicious executable and compress it into a
payload.zipfile. - Host the
payload.zipfile on an attacker-controlled server (https://attacker.evil/payload.zip), ensuring the server responds with aContent-Disposition: attachment; filename="payload.zip"header. - Prepare a webpage that includes a subresource (e.g., an
<img>tag) with asrcpointing to a redirector on the trusted Intranet domain (e.g.,https://internal.example.com/redirect?url=https://attacker.evil/payload.zip). - Open the webpage in Chrome.
- Press Ctrl+S and save the page as “Webpage, Complete”.
- Navigate to the associated
_filesfolder created by Chrome. - Check the Zone Identifier for the downloaded
payload.zipfile using PowerShell:Get-Content -Path 'C:\path\to\saved_page_files\payload.zip' -Stream Zone.Identifier - Observe that
ZoneId=1andHostUrlpoints tointernal.example.com. - Extract the ZIP file using Windows Explorer. Verify the extracted executable also has
ZoneId=1. - Run the extracted executable to confirm SmartScreen warnings are bypassed.
Suggested Fix
The quarantine_callback should not capture the URL by value before the network request begins. Instead, the callback signature should be updated to accept the final URL as a parameter when invoked at the end of the download. Alternatively, SimpleURLLoaderHelper::OnResponseStarted should re-bind the quarantine_callback with the final_url before passing it to SaveFileCreateInfo.
Evaluated with Chrome root at commit: eca8648a4e1cdfdda68c495a6003059fed641955
Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; 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.