diff --git a/net/device_bound_sessions/registration_fetcher.cc b/net/device_bound_sessions/registration_fetcher.cc index 214e2b2f48e9b..4b2bcce0b8da2 100644 --- a/net/device_bound_sessions/registration_fetcher.cc +++ b/net/device_bound_sessions/registration_fetcher.cc @@ -723,12 +723,20 @@ class RegistrationFetcherImpl : public RegistrationFetcher { // same-site with the scope origin. But we still need to validate // that this subdomain is allowed to register a session for the // whole site. + // + // SECURITY: Use the *final* (post-redirect) response URL — i.e. the origin + // that actually served the session config — not `fetcher_endpoint_`. The + // latter is fixed at construction and can be pointed at an open redirect + // on the apex by a same-site subdomain via the Secure-Session-Registration + // `path` parameter, which would cause this check to compare the apex host + // to itself and skip the well-known authorization entirely. + const GURL final_registration_url = url_fetcher_->request().url(); if (features::kDeviceBoundSessionsCheckSubdomainRegistration.Get() && !IsForRefreshRequest() && params_or_error->scope.include_site && // Skip all validations if the fetcher endpoint is not a subdomain but // rather the top-level site (which matches the origin when including // the site). - fetcher_endpoint_.GetHost() != session->origin().host()) { + final_registration_url.host() != session->origin().host()) { GURL::Replacements replacements; replacements.SetPathStr("/.well-known/device-bound-sessions"); replacements.SetHostStr(session->origin().host()); @@ -745,7 +753,8 @@ class RegistrationFetcherImpl : public RegistrationFetcher { url_fetcher_->Start( base::BindOnce(&RegistrationFetcherImpl:: OnSubdomainRegistrationWellKnownRequestComplete, - GetWeakPtr(), std::move(session))); + GetWeakPtr(), final_registration_url, + std::move(session))); return; } @@ -754,13 +763,15 @@ class RegistrationFetcherImpl : public RegistrationFetcher { } void OnSubdomainRegistrationWellKnownRequestComplete( + const GURL& final_registration_url, std::unique_ptr session) { RunCallback(OnSubdomainRegistrationWellKnownRequestCompleteInternal( - std::move(session))); + final_registration_url, std::move(session))); // `this` may be deleted. } RegistrationResult OnSubdomainRegistrationWellKnownRequestCompleteInternal( + const GURL& final_registration_url, std::unique_ptr session) { HttpResponseHeaders* headers = url_fetcher_->request().response_headers(); const int response_code = headers ? headers->response_code() : 0; @@ -788,7 +799,7 @@ class RegistrationFetcherImpl : public RegistrationFetcher { if (!maybe_params->registering_origins.has_value() || !std::ranges::contains( *maybe_params->registering_origins, - url::Origin::Create(fetcher_endpoint_).Serialize())) { + url::Origin::Create(final_registration_url).Serialize())) { return CreateErrorRegistrationResult( SessionError(SessionError::kSubdomainRegistrationUnauthorized)); }