CVE-2026-113049
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
MixedContentCheckerShouldBlockNavigationWithBlockableContextTestcontent/browser/renderer_host/mixed_content_checker_unittest.cc |
modified | |
ifcontent/test/navigation_simulator_impl.cc |
modified |
Files Changed
content/test/content_test_bundle_data.filelistcontent/browser/renderer_host/mixed_content_checker.cccontent/test/navigation_simulator_impl.hcontent/browser/site_per_process_mixed_content_browsertest.cccontent/test/data/mixed-content/navigate-iframe-to-insecure.htmlcontent/browser/renderer_host/mixed_content_checker_unittest.cccontent/test/navigation_simulator_impl.cc
Patch
From bf05de1c1c82a35dda6b57208012c1d4e7f237fc Mon Sep 17 00:00:00 2001
From: Carlos IL <carlosil@chromium.org>
Date: Tue, 15 Sep 2026 10:25:42 -0700
Subject: [PATCH] Do not send source location for cross-origin mixed content
When mixed content is encountered during navigation, the browser
notifies the navigating frame's renderer and includes the source
location of the navigation initiator. The full initiator URL, along
with line and column numbers, was forwarded even when the initiator
was cross-origin with the navigating frame.
Bug: 537465180
Change-Id: I359e324f8fbcd300de026ad474bc403535629d7f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8382818
Reviewed-by: Camille Lamy <clamy@chromium.org>
Commit-Queue: Carlos IL <carlosil@chromium.org>
Reviewed-by: Mustafa Emre Acer <meacer@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1698224}
---
diff --git a/content/browser/renderer_host/mixed_content_checker.cc b/content/browser/renderer_host/mixed_content_checker.cc
index f4f1c934..b9c5b69 100644
--- a/content/browser/renderer_host/mixed_content_checker.cc
+++ b/content/browser/renderer_host/mixed_content_checker.cc
@@ -18,6 +18,7 @@
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/ip_address_space_util.h"
#include "services/network/public/cpp/is_potentially_trustworthy.h"
+#include "services/network/public/mojom/source_location.mojom.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/security_context/insecure_request_policy.h"
#include "third_party/blink/public/mojom/loader/mixed_content.mojom.h"
@@ -121,11 +122,24 @@
CHECK(!navigation_request->GetRedirectChain().empty(),
base::NotFatalUntil::M158);
GURL url_before_redirects = navigation_request->GetRedirectChain()[0];
- rfh->GetAssociatedLocalFrame()->MixedContentFound(
- mixed_content_url, navigation_request->GetURL(),
- navigation_request->request_context_type(), was_allowed,
- url_before_redirects, for_redirect,
- navigation_request->common_params().source_location.Clone());
+ // The source location identifies the document or script that initiated the
+ // navigation, which can be in a different process from the navigating
+ // frame, so it should not be sent if it's cross-origin with the navigating
+ // frame's document.
+ network::mojom::SourceLocationPtr source_location =
+ navigation_request->common_params().source_location.Clone();
+ if (rfh->GetLastCommittedOrigin().IsSameOriginWith(
+ GURL(source_location->url))) {
+ rfh->GetAssociatedLocalFrame()->MixedContentFound(
+ mixed_content_url, navigation_request->GetURL(),
+ navigation_request->request_context_type(), was_allowed,
+ url_before_redirects, for_redirect, std::move(source_location));
+ } else {
+ rfh->GetAssociatedLocalFrame()->MixedContentFound(
+ mixed_content_url, navigation_request->GetURL(),
+ navigation_request->request_context_type(), was_allowed,
+ url_before_redirects, for_redirect, nullptr);
+ }
}
// Updates the renderer about any Blink feature usage.
diff --git a/content/browser/renderer_host/mixed_content_checker_unittest.cc b/content/browser/renderer_host/mixed_content_checker_unittest.cc
index 7f6b1df..8630ad7b 100644
--- a/content/browser/renderer_host/mixed_content_checker_unittest.cc
+++ b/content/browser/renderer_host/mixed_content_checker_unittest.cc
@@ -18,7 +18,7 @@
#include "content/test/test_render_frame_host.h"
#include "content/test/test_render_view_host.h"
#include "services/network/public/cpp/features.h"
-#include "services/network/public/mojom/source_location.mojom-forward.h"
+#include "services/network/public/mojom/source_location.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom.h"
@@ -63,6 +63,7 @@
network::mojom::SourceLocationPtr source_location) final {
mixed_content_result_ = MixedContentResult{
main_resource_url, mixed_content_url, was_allowed, had_redirect};
+ source_location_ = std::move(source_location);
}
void ReportBlinkFeatureUsage(
const std::vector<blink::mojom::WebFeature>& web_features) final {
@@ -72,6 +73,9 @@
const std::optional<MixedContentResult>& mixed_content_result() const {
return mixed_content_result_;
}
+ const network::mojom::SourceLocationPtr& source_location() const {
+ return source_location_;
+ }
const std::vector<blink::mojom::WebFeature>& reported_web_features() const {
return reported_web_features_;
}
@@ -82,6 +86,7 @@
raw_ptr<TestRenderFrameHost> rfh_;
std::vector<blink::mojom::WebFeature> reported_web_features_;
std::optional<MixedContentResult> mixed_content_result_;
+ network::mojom::SourceLocationPtr source_location_;
};
// Needed by GTest to display errors.
@@ -354,6 +359,90 @@
/*was_allowed=*/false, for_redirect())));
}
+// The source location reported to the renderer identifies the document or
+// script that initiated the navigation. When it is cross-origin with the
+// navigating frame's current document, it should not be reported.
+TEST_P(MixedContentCheckerShouldBlockNavigationTest,
+ ReportsSourceLocationOriginToCrossOriginRenderer) {
+ if (!AreAllSitesIsolatedForTesting()) {
+ GTEST_SKIP() << "Site isolation is required for this test.";
+ }
+
+ NavigateAndCommit(GURL("https://source.com"));
+ TestRenderFrameHost* main_rfh = main_test_rfh();
+ main_rfh->DidEnforceInsecureRequestPolicy(
+ blink::mojom::InsecureRequestPolicy::kLeaveInsecureRequestsAlone);
+
+ TestRenderFrameHost* subframe = static_cast<TestRenderFrameHost*>(
+ NavigationSimulator::NavigateAndCommitFromDocument(
+ GURL("https://other.com/subframe"),
+ main_rfh->AppendChild("subframe")));
+ ASSERT_NE(subframe->GetProcess(), main_rfh->GetProcess());
+ auto interceptor = std::make_unique<LocalFrameInterceptor>(subframe);
+
+ std::unique_ptr<NavigationSimulatorImpl> navigation =
+ NavigationSimulatorImpl::CreateRendererInitiated(
+ GURL("http://target.com"), subframe);
+ navigation->SetReferrer(blink::mojom::Referrer::New(
+ subframe->GetLastCommittedURL(),
+ network::mojom::ReferrerPolicy::kStrictOriginWhenCrossOrigin));
+ navigation->set_request_context_type(
+ blink::mojom::RequestContextType::INTERNAL);
+ navigation->set_mixed_content_context_type(
+ blink::mojom::MixedContentContextType::kBlockable);
+ navigation->set_source_location(network::mojom::SourceLocation::New(
+ "https://source.com/private/script.js?token=value", 42u, 7u));
+ navigation->Start();
+
+ auto checker = MixedContentChecker();
+ EXPECT_TRUE(checker.ShouldBlockNavigation(*navigation->GetNavigationHandle(),
+ for_redirect()));
+ interceptor->FlushLocalFrameMessages();
+ ASSERT_FALSE(interceptor->source_location());
+}
+
+// When the source location of the navigation is same-origin with the
+// navigating frame's current document, its full URL is reported to the
+// renderer (which already has access to it).
+TEST_P(MixedContentCheckerShouldBlockNavigationTest,
+ ReportsSourceLocationFullUrlToSameOriginRenderer) {
+ NavigateAndCommit(GURL("https://source.com"));
+ TestRenderFrameHost* main_rfh = main_test_rfh();
+ main_rfh->DidEnforceInsecureRequestPolicy(
+ blink::mojom::InsecureRequestPolicy::kLeaveInsecureRequestsAlone);
+
+ TestRenderFrameHost* subframe = static_cast<TestRenderFrameHost*>(
+ NavigationSimulator::NavigateAndCommitFromDocument(
+ GURL("https://source.com/subframe"),
+ main_rfh->AppendChild("subframe")));
+ ASSERT_EQ(subframe->GetProcess(), main_rfh->GetProcess());
+ auto interceptor = std::make_unique<LocalFrameInterceptor>(subframe);
+
+ std::unique_ptr<NavigationSimulatorImpl> navigation =
+ NavigationSimulatorImpl::CreateRendererInitiated(
+ GURL("http://target.com"), subframe);
+ navigation->SetReferrer(blink::mojom::Referrer::New(
+ subframe->GetLastCommittedURL(),
+ network::mojom::ReferrerPolicy::kStrictOriginWhenCrossOrigin));
+ navigation->set_request_context_type(
+ blink::mojom::RequestContextType::INTERNAL);
+ navigation->set_mixed_content_context_type(
+ blink::mojom::MixedContentContextType::kBlockable);
+ navigation->set_source_location(network::mojom::SourceLocation::New(
+ "https://source.com/private/script.js?token=value", 42u, 7u));
+ navigation->Start();
+
+ auto checker = MixedContentChecker();
+ EXPECT_TRUE(checker.ShouldBlockNavigation(*navigation->GetNavigationHandle(),
+ for_redirect()));
+ interceptor->FlushLocalFrameMessages();
+ ASSERT_TRUE(interceptor->source_location());
+ EXPECT_EQ("https://source.com/private/script.js?token=value",
+ interceptor->source_location()->url);
+ EXPECT_EQ(42u, interceptor->source_location()->line);
+ EXPECT_EQ(7u, interceptor->source_location()->column);
+}
+
// Tests to cover MixedContentContextType = kBlockable.
class MixedContentCheckerShouldBlockNavigationWithBlockableContextTest
: public MixedContentCheckerShouldBlockNavigationTestBase {
diff --git a/content/browser/site_per_process_mixed_content_browsertest.cc b/content/browser/site_per_process_mixed_content_browsertest.cc
index d9599ed..64f1642 100644
--- a/content/browser/site_per_process_mixed_content_browsertest.cc
+++ b/content/browser/site_per_process_mixed_content_browsertest.cc
@@ -2,14 +2,16 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "content/browser/site_per_process_browsertest.h"
-
+#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
+#include "content/browser/site_per_process_browsertest.h"
#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/content_mock_cert_verifier.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/test/render_document_feature.h"
+#include "url/origin.h"
namespace content {
@@ -142,6 +144,43 @@
EXPECT_TRUE(mixed_child->is_on_initial_empty_document());
}
+// Tests the console message logged in a cross-site OOPIF when a document
+// navigates the OOPIF to insecure content. The message is reported on the
+// iframe's current document, which is cross-origin with the initiating
+// document, so its source location should not be reported.
+IN_PROC_BROWSER_TEST_P(SitePerProcessIgnoreCertErrorsBrowserTest,
+ ActiveMixedContentInCrossSiteIframeConsoleMessage) {
+ net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
+ https_server.ServeFilesFromSourceDirectory(GetTestDataFilePath());
+ SetupCrossSiteRedirector(&https_server);
+ ASSERT_TRUE(https_server.Start());
+
+ GURL main_url(https_server.GetURL(
+ "a.com", "/mixed-content/navigate-iframe-to-insecure.html?token=value"));
+ EXPECT_TRUE(NavigateToURL(shell(), main_url));
+
+ FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
+ ASSERT_EQ(1U, root->child_count());
+ FrameTreeNode* child = root->child_at(0);
+ ASSERT_NE(child->current_frame_host()->GetProcess(),
+ root->current_frame_host()->GetProcess());
+
+ WebContentsConsoleObserver console_observer(web_contents());
+ console_observer.SetPattern("Mixed Content:*");
+ EXPECT_TRUE(
+ ExecJs(root, "navigateIframeTo('http://insecure.test/title1.html');"));
+ ASSERT_TRUE(console_observer.Wait());
+
+ ASSERT_EQ(1u, console_observer.messages().size());
+ const WebContentsConsoleObserver::Message& message =
+ console_observer.messages()[0];
+ EXPECT_EQ(child->current_frame_host(), message.source_frame);
+ EXPECT_EQ(base::ASCIIToUTF16(
+ child->current_frame_host()->GetLastCommittedURL().spec()),
+ message.source_id);
+ EXPECT_EQ(0, message.line_no);
+}
+
// Tests that the WebContents is notified when passive mixed content is
// displayed in an OOPIF. The test ignores cert errors so that an HTTPS
// iframe can be loaded from a site other than localhost (the
diff --git a/content/test/content_test_bundle_data.filelist b/content/test/content_test_bundle_data.filelist
index 14c9365..5e909d7 100644
--- a/content/test/content_test_bundle_data.filelist
+++ b/content/test/content_test_bundle_data.filelist
@@ -8965,6 +8965,7 @@
data/mixed-content/image-with-non-redundant-cert-error.html
data/mixed-content/image-with-redundant-cert-error-in-iframe.html
data/mixed-content/image-with-redundant-cert-error.html
+data/mixed-content/navigate-iframe-to-insecure.html
data/mixed-content/non-redundant-cert-error-in-iframe.html
data/mixed-content/redundant-cert-error-in-iframe.html
data/mojo_bindings_web_test.test-mojom
diff --git a/content/test/data/mixed-content/navigate-iframe-to-insecure.html b/content/test/data/mixed-content/navigate-iframe-to-insecure.html
new file mode 100644
index 0000000..65995aa
--- /dev/null
+++ b/content/test/data/mixed-content/navigate-iframe-to-insecure.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<body>
+<iframe src="/cross-site/b.com/title1.html"></iframe>
+<script>
+function navigateIframeTo(url) {
+ document.querySelector('iframe').src = url;
+}
+</script>
+</body>
+</html>
diff --git a/content/test/navigation_simulator_impl.cc b/content/test/navigation_simulator_impl.cc
index c853504..6dfe9fb5 100644
--- a/content/test/navigation_simulator_impl.cc
+++ b/content/test/navigation_simulator_impl.cc
@@ -1482,6 +1482,9 @@
common_params->href_translate = href_translate_;
common_params->request_destination =
network::mojom::RequestDestination::kDocument;
+ if (source_location_) {
+ common_params->source_location = source_location_.Clone();
+ }
mojo::PendingAssociatedRemote<mojom::NavigationClient>
navigation_client_remote;
diff --git a/content/test/navigation_simulator_impl.h b/content/test/navigation_simulator_impl.h
index d41c607..f9fec30 100644
--- a/content/test/navigation_simulator_impl.h
+++ b/content/test/navigation_simulator_impl.h
@@ -26,6 +26,7 @@
#include "net/dns/public/resolve_error_info.h"
#include "net/http/http_connection_info.h"
#include "services/network/public/cpp/permissions_policy/permissions_policy_declaration.h"
+#include "services/network/public/mojom/source_location.mojom.h"
#include "third_party/blink/public/mojom/loader/mixed_content.mojom.h"
#include "third_party/blink/public/mojom/loader/referrer.mojom-forward.h"
#include "url/gurl.h"
@@ -213,6 +214,12 @@
request_context_type_ = request_context_type;
}
+ // Sets the source location reported for the navigation. Only supported for
+ // renderer-initiated navigations.
+ void set_source_location(network::mojom::SourceLocationPtr source_location) {
+ source_location_ = std::move(source_location);
+ }
+
void set_insecure_request_policy(
blink::mojom::InsecureRequestPolicy insecure_request_policy) {
insecure_request_policy_ = insecure_request_policy;
@@ -403,6 +410,7 @@
std::string href_translate_;
blink::mojom::RequestContextType request_context_type_ =
blink::mojom::RequestContextType::LOCATION;
+ network::mojom::SourceLocationPtr source_location_;
blink::mojom::InsecureRequestPolicy insecure_request_policy_ =
blink::mojom::InsecureRequestPolicy::kLeaveInsecureRequestsAlone;
std::vector<uint32_t> insecure_navigations_set_;
Regression Test / PoC
diff --git a/content/browser/renderer_host/mixed_content_checker_unittest.cc b/content/browser/renderer_host/mixed_content_checker_unittest.cc
index 7f6b1df..8630ad7b 100644
--- a/content/browser/renderer_host/mixed_content_checker_unittest.cc
+++ b/content/browser/renderer_host/mixed_content_checker_unittest.cc
@@ -18,7 +18,7 @@
#include "content/test/test_render_frame_host.h"
#include "content/test/test_render_view_host.h"
#include "services/network/public/cpp/features.h"
-#include "services/network/public/mojom/source_location.mojom-forward.h"
+#include "services/network/public/mojom/source_location.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom.h"
@@ -63,6 +63,7 @@
network::mojom::SourceLocationPtr source_location) final {
mixed_content_result_ = MixedContentResult{
main_resource_url, mixed_content_url, was_allowed, had_redirect};
+ source_location_ = std::move(source_location);
}
void ReportBlinkFeatureUsage(
const std::vector<blink::mojom::WebFeature>& web_features) final {
@@ -72,6 +73,9 @@
const std::optional<MixedContentResult>& mixed_content_result() const {
return mixed_content_result_;
}
+ const network::mojom::SourceLocationPtr& source_location() const {
+ return source_location_;
+ }
const std::vector<blink::mojom::WebFeature>& reported_web_features() const {
return reported_web_features_;
}
@@ -82,6 +86,7 @@
raw_ptr<TestRenderFrameHost> rfh_;
std::vector<blink::mojom::WebFeature> reported_web_features_;
std::optional<MixedContentResult> mixed_content_result_;
+ network::mojom::SourceLocationPtr source_location_;
};
// Needed by GTest to display errors.
@@ -354,6 +359,90 @@
/*was_allowed=*/false, for_redirect())));
}
+// The source location reported to the renderer identifies the document or
+// script that initiated the navigation. When it is cross-origin with the
+// navigating frame's current document, it should not be reported.
+TEST_P(MixedContentCheckerShouldBlockNavigationTest,
+ ReportsSourceLocationOriginToCrossOriginRenderer) {
+ if (!AreAllSitesIsolatedForTesting()) {
+ GTEST_SKIP() << "Site isolation is required for this test.";
+ }
+
+ NavigateAndCommit(GURL("https://source.com"));
+ TestRenderFrameHost* main_rfh = main_test_rfh();
+ main_rfh->DidEnforceInsecureRequestPolicy(
+ blink::mojom::InsecureRequestPolicy::kLeaveInsecureRequestsAlone);
+
+ TestRenderFrameHost* subframe = static_cast<TestRenderFrameHost*>(
+ NavigationSimulator::NavigateAndCommitFromDocument(
+ GURL("https://other.com/subframe"),
+ main_rfh->AppendChild("subframe")));
+ ASSERT_NE(subframe->GetProcess(), main_rfh->GetProcess());
+ auto interceptor = std::make_unique<LocalFrameInterceptor>(subframe);
+
+ std::unique_ptr<NavigationSimulatorImpl> navigation =
+ NavigationSimulatorImpl::CreateRendererInitiated(
+ GURL("http://target.com"), subframe);
+ navigation->SetReferrer(blink::mojom::Referrer::New(
+ subframe->GetLastCommittedURL(),
+ network::mojom::ReferrerPolicy::kStrictOriginWhenCrossOrigin));
+ navigation->set_request_context_type(
+ blink::mojom::RequestContextType::INTERNAL);
+ navigation->set_mixed_content_context_type(
+ blink::mojom::MixedContentContextType::kBlockable);
+ navigation->set_source_location(network::mojom::SourceLocation::New(
+ "https://source.com/private/script.js?token=value", 42u, 7u));
+ navigation->Start();
+
+ auto checker = MixedContentChecker();
+ EXPECT_TRUE(checker.ShouldBlockNavigation(*navigation->GetNavigationHandle(),
+ for_redirect()));
+ interceptor->FlushLocalFrameMessages();
+ ASSERT_FALSE(interceptor->source_location());
+}
+
+// When the source location of the navigation is same-origin with the
+// navigating frame's current document, its full URL is reported to the
+// renderer (which already has access to it).
+TEST_P(MixedContentCheckerShouldBlockNavigationTest,
+ ReportsSourceLocationFullUrlToSameOriginRenderer) {
+ NavigateAndCommit(GURL("https://source.com"));
+ TestRenderFrameHost* main_rfh = main_test_rfh();
+ main_rfh->DidEnforceInsecureRequestPolicy(
+ blink::mojom::InsecureRequestPolicy::kLeaveInsecureRequestsAlone);
+
+ TestRenderFrameHost* subframe = static_cast<TestRenderFrameHost*>(
+ NavigationSimulator::NavigateAndCommitFromDocument(
+ GURL("https://source.com/subframe"),
+ main_rfh->AppendChild("subframe")));
+ ASSERT_EQ(subframe->GetProcess(), main_rfh->GetProcess());
+ auto interceptor = std::make_unique<LocalFrameInterceptor>(subframe);
+
+ std::unique_ptr<NavigationSimulatorImpl> navigation =
+ NavigationSimulatorImpl::CreateRendererInitiated(
+ GURL("http://target.com"), subframe);
+ navigation->SetReferrer(blink::mojom::Referrer::New(
+ subframe->GetLastCommittedURL(),
+ network::mojom::ReferrerPolicy::kStrictOriginWhenCrossOrigin));
+ navigation->set_request_context_type(
+ blink::mojom::RequestContextType::INTERNAL);
+ navigation->set_mixed_content_context_type(
+ blink::mojom::MixedContentContextType::kBlockable);
+ navigation->set_source_location(network::mojom::SourceLocation::New(
+ "https://source.com/private/script.js?token=value", 42u, 7u));
+ navigation->Start();
+
+ auto checker = MixedContentChecker();
+ EXPECT_TRUE(checker.ShouldBlockNavigation(*navigation->GetNavigationHandle(),
+ for_redirect()));
+ interceptor->FlushLocalFrameMessages();
+ ASSERT_TRUE(interceptor->source_location());
+ EXPECT_EQ("https://source.com/private/script.js?token=value",
+ interceptor->source_location()->url);
+ EXPECT_EQ(42u, interceptor->source_location()->line);
+ EXPECT_EQ(7u, interceptor->source_location()->column);
+}
+
// Tests to cover MixedContentContextType = kBlockable.
class MixedContentCheckerShouldBlockNavigationWithBlockableContextTest
: public MixedContentCheckerShouldBlockNavigationTestBase {
diff --git a/content/browser/site_per_process_mixed_content_browsertest.cc b/content/browser/site_per_process_mixed_content_browsertest.cc
index d9599ed..64f1642 100644
--- a/content/browser/site_per_process_mixed_content_browsertest.cc
+++ b/content/browser/site_per_process_mixed_content_browsertest.cc
@@ -2,14 +2,16 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "content/browser/site_per_process_browsertest.h"
-
+#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
+#include "content/browser/site_per_process_browsertest.h"
#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/content_mock_cert_verifier.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/test/render_document_feature.h"
+#include "url/origin.h"
namespace content {
@@ -142,6 +144,43 @@
EXPECT_TRUE(mixed_child->is_on_initial_empty_document());
}
+// Tests the console message logged in a cross-site OOPIF when a document
+// navigates the OOPIF to insecure content. The message is reported on the
+// iframe's current document, which is cross-origin with the initiating
+// document, so its source location should not be reported.
+IN_PROC_BROWSER_TEST_P(SitePerProcessIgnoreCertErrorsBrowserTest,
+ ActiveMixedContentInCrossSiteIframeConsoleMessage) {
+ net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
+ https_server.ServeFilesFromSourceDirectory(GetTestDataFilePath());
+ SetupCrossSiteRedirector(&https_server);
+ ASSERT_TRUE(https_server.Start());
+
+ GURL main_url(https_server.GetURL(
+ "a.com", "/mixed-content/navigate-iframe-to-insecure.html?token=value"));
+ EXPECT_TRUE(NavigateToURL(shell(), main_url));
+
+ FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
+ ASSERT_EQ(1U, root->child_count());
+ FrameTreeNode* child = root->child_at(0);
+ ASSERT_NE(child->current_frame_host()->GetProcess(),
+ root->current_frame_host()->GetProcess());
+
+ WebContentsConsoleObserver console_observer(web_contents());
+ console_observer.SetPattern("Mixed Content:*");
+ EXPECT_TRUE(
+ ExecJs(root, "navigateIframeTo('http://insecure.test/title1.html');"));
+ ASSERT_TRUE(console_observer.Wait());
+
+ ASSERT_EQ(1u, console_observer.messages().size());
+ const WebContentsConsoleObserver::Message& message =
+ console_observer.messages()[0];
+ EXPECT_EQ(child->current_frame_host(), message.source_frame);
+ EXPECT_EQ(base::ASCIIToUTF16(
+ child->current_frame_host()->GetLastCommittedURL().spec()),
+ message.source_id);
+ EXPECT_EQ(0, message.line_no);
+}
+
// Tests that the WebContents is notified when passive mixed content is
// displayed in an OOPIF. The test ignores cert errors so that an HTTPS
// iframe can be loaded from a site other than localhost (the
diff --git a/content/test/content_test_bundle_data.filelist b/content/test/content_test_bundle_data.filelist
index 14c9365..5e909d7 100644
--- a/content/test/content_test_bundle_data.filelist
+++ b/content/test/content_test_bundle_data.filelist
@@ -8965,6 +8965,7 @@
data/mixed-content/image-with-non-redundant-cert-error.html
data/mixed-content/image-with-redundant-cert-error-in-iframe.html
data/mixed-content/image-with-redundant-cert-error.html
+data/mixed-content/navigate-iframe-to-insecure.html
data/mixed-content/non-redundant-cert-error-in-iframe.html
data/mixed-content/redundant-cert-error-in-iframe.html
data/mojo_bindings_web_test.test-mojom
diff --git a/content/test/data/mixed-content/navigate-iframe-to-insecure.html b/content/test/data/mixed-content/navigate-iframe-to-insecure.html
new file mode 100644
index 0000000..65995aa
--- /dev/null
+++ b/content/test/data/mixed-content/navigate-iframe-to-insecure.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<body>
+<iframe src="/cross-site/b.com/title1.html"></iframe>
+<script>
+function navigateIframeTo(url) {
+ document.querySelector('iframe').src = url;
+}
+</script>
+</body>
+</html>
diff --git a/content/test/navigation_simulator_impl.cc b/content/test/navigation_simulator_impl.cc
index c853504..6dfe9fb5 100644
--- a/content/test/navigation_simulator_impl.cc
+++ b/content/test/navigation_simulator_impl.cc
@@ -1482,6 +1482,9 @@
common_params->href_translate = href_translate_;
common_params->request_destination =
network::mojom::RequestDestination::kDocument;
+ if (source_location_) {
+ common_params->source_location = source_location_.Clone();
+ }
mojo::PendingAssociatedRemote<mojom::NavigationClient>
navigation_client_remote;
diff --git a/content/test/navigation_simulator_impl.h b/content/test/navigation_simulator_impl.h
index d41c607..f9fec30 100644
--- a/content/test/navigation_simulator_impl.h
+++ b/content/test/navigation_simulator_impl.h
@@ -26,6 +26,7 @@
#include "net/dns/public/resolve_error_info.h"
#include "net/http/http_connection_info.h"
#include "services/network/public/cpp/permissions_policy/permissions_policy_declaration.h"
+#include "services/network/public/mojom/source_location.mojom.h"
#include "third_party/blink/public/mojom/loader/mixed_content.mojom.h"
#include "third_party/blink/public/mojom/loader/referrer.mojom-forward.h"
#include "url/gurl.h"
@@ -213,6 +214,12 @@
request_context_type_ = request_context_type;
}
+ // Sets the source location reported for the navigation. Only supported for
+ // renderer-initiated navigations.
+ void set_source_location(network::mojom::SourceLocationPtr source_location) {
+ source_location_ = std::move(source_location);
+ }
+
void set_insecure_request_policy(
blink::mojom::InsecureRequestPolicy insecure_request_policy) {
insecure_request_policy_ = insecure_request_policy;
@@ -403,6 +410,7 @@
std::string href_translate_;
blink::mojom::RequestContextType request_context_type_ =
blink::mojom::RequestContextType::LOCATION;
+ network::mojom::SourceLocationPtr source_location_;
blink::mojom::InsecureRequestPolicy insecure_request_policy_ =
blink::mojom::InsecureRequestPolicy::kLeaveInsecureRequestsAlone;
std::vector<uint32_t> insecure_navigations_set_;
Original Bug Report
Cross-origin URL disclosure of navigation initiator via MixedContentFound IPC
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 browser-side mixed content checker transmits the navigation initiator’s unsanitized script/document URL (including path and query parameters) to a subframe’s current renderer process via the MixedContentFound Mojo interface. If the subframe belongs to a different, potentially compromised renderer process, this allows cross-origin sensitive data leakage across the Site Isolation boundary. This issue is a potential vulnerability as we do not yet possess a fully running proof of concept.
Affected files:
content/browser/renderer_host/mixed_content_checker.cc
Estimated timestamp from git blame: 2017-03-20
Description
There is a potential cross-origin information disclosure vulnerability in the browser-side mixed-content reporting mechanism. When a page triggers an insecure navigation in a cross-origin subframe, the browser-side mixed-content checker notifies the subframe’s current renderer of the event. However, the Mojo call MixedContentFound includes the navigation initiator’s full script or document URL (navigation_request->common_params().source_location) without sanitization.
While the URL of the mixed content itself (mixed_content_url) is correctly sanitized based on whether the frames share a SiteInstanceGroup, the accompanying source_location is cloned and sent verbatim. A compromised renderer process owning the subframe can capture this IPC message to extract sensitive query parameters or session tokens belonging to the cross-origin initiator.
Root Cause
In content/browser/renderer_host/mixed_content_checker.cc:
void UpdateRendererOnMixedContentFound(NavigationRequest* navigation_request,
RenderFrameHostImpl* mixed_content_frame,
bool was_allowed,
bool for_redirect) {
...
RenderFrameHostImpl* rfh =
navigation_request->frame_tree_node()->current_frame_host();
// mixed_content_url is sanitized
GURL mixed_content_url =
mixed_content_frame->GetSiteInstance()->group() ==
rfh->GetSiteInstance()->group()
? mixed_content_frame->GetLastCommittedURL()
: mixed_content_frame->GetLastCommittedOrigin().GetURL();
...
rfh->GetAssociatedLocalFrame()->MixedContentFound(
mixed_content_url, navigation_request->GetURL(),
navigation_request->request_context_type(), was_allowed,
url_before_redirects, for_redirect,
navigation_request->common_params().source_location.Clone()); // <--- Passed unsanitized
}
Because the navigation throttle runs at WillStartRequest, the target frame’s current_frame_host() (rfh) is still the pre-navigation active document (the attacker’s subframe). The browser process clones the initiator’s source_location and sends it to rfh’s renderer without cross-origin checks.
Suggested Potential Exploitation Steps
- A sensitive page at
https://victim.example/dashboard?token=SECRETembeds an attacker-controlled subframehttps://evil.example/widgetwhich is hosted in a separate sandboxed renderer process under Site Isolation. - A script on the victim page at
https://victim.example/js/bundle.js?auth=12345navigates the subframe to an insecure plain HTTP URL (e.g.,document.getElementById('subframe').src = 'http://plaintext.test/'). - The browser process intercepting this navigation creates a
NavigationRequestcarrying the initiator’s source location (https://victim.example/js/bundle.js?auth=12345). - The browser’s
MixedContentNavigationThrottleruns and callsUpdateRendererOnMixedContentFound. - The browser process sends the
MixedContentFoundMojo message to theevil.examplerenderer process. This message contains the unsanitizedsource_locationURL. - A compromised
evil.examplerenderer process intercepts the message and extracts the initiator’s full URL, exposing the sensitive query token.
(Note: These are suggested steps; our tooling does not currently have the capability to run a live exploit or PoC.)
Suggested Fix
Sanitize the source_location parameter in UpdateRendererOnMixedContentFound before cloning/sending it, similar to the sanitization performed in Content Security Policy (CSP) violation reporting (RenderFrameHostCSPContext::SanitizeDataForUseInCspViolation). Specifically, if the rfh’s origin is not same-origin with the source_location’s URL, the URL should be stripped to its origin and line/column numbers cleared:
network::mojom::SourceLocationPtr source_location =
navigation_request->common_params().source_location.Clone();
if (source_location && rfh) {
GURL source_url(source_location->url);
if (!rfh->GetLastCommittedOrigin().IsSameOriginWith(source_url)) {
source_location->url = source_url.DeprecatedGetOriginAsURL().spec();
source_location->line = 0u;
source_location->column = 0u;
}
}
Evaluated with Chrome root at commit: bf775e5d75cb9e1767e2cd02cc93efa0077d14a5
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.