Commit 52bc9b90 authored by Mustafa Emre Acer's avatar Mustafa Emre Acer Committed by Commit Bot

Lookalikes: Properly handle interstitial reloads

When the lookalike URL interstitial stops a redirect, reloading it navigates to
the end of the redirect chain which hides the interstitial.

In order to prevent this, this CL stores the parameters of the interstitial
(URL, redirect chain and referrer) as WebContentsUserData. On page reload,
the throttle retrieves the parameters and checks if the currently navigated URL
is at the end of the stored redirect chain. If so, it cancels the current
navigation (the reload) and navigates back to the original lookalike URL that
triggered the interstitial.

As a result, a reload will end up with the same
interstitial as before. The throttle immediately clears stored interstitial
parameters after it retrieves them, so that unrelated navigations (to a
different URL, or initiated directly by the user) don't re-trigger the
interstitial.

Bug: 941886
Change-Id: If37802815f296bf534d7fb3b54fe96813d1659d7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1715759
Commit-Queue: Mustafa Emre Acer <meacer@chromium.org>
Reviewed-by: default avatarJoe DeBlasio <jdeblasio@chromium.org>
Cr-Commit-Position: refs/heads/master@{#682093}
parent a21c3e1e
...@@ -620,8 +620,6 @@ jumbo_split_static_library("browser") { ...@@ -620,8 +620,6 @@ jumbo_split_static_library("browser") {
"lifetime/application_lifetime_mac.mm", "lifetime/application_lifetime_mac.mm",
"lifetime/browser_shutdown.cc", "lifetime/browser_shutdown.cc",
"lifetime/browser_shutdown.h", "lifetime/browser_shutdown.h",
"lookalikes/lookalike_url_allowlist.cc",
"lookalikes/lookalike_url_allowlist.h",
"lookalikes/lookalike_url_controller_client.cc", "lookalikes/lookalike_url_controller_client.cc",
"lookalikes/lookalike_url_controller_client.h", "lookalikes/lookalike_url_controller_client.h",
"lookalikes/lookalike_url_interstitial_page.cc", "lookalikes/lookalike_url_interstitial_page.cc",
...@@ -630,6 +628,8 @@ jumbo_split_static_library("browser") { ...@@ -630,6 +628,8 @@ jumbo_split_static_library("browser") {
"lookalikes/lookalike_url_navigation_throttle.h", "lookalikes/lookalike_url_navigation_throttle.h",
"lookalikes/lookalike_url_service.cc", "lookalikes/lookalike_url_service.cc",
"lookalikes/lookalike_url_service.h", "lookalikes/lookalike_url_service.h",
"lookalikes/lookalike_url_tab_storage.cc",
"lookalikes/lookalike_url_tab_storage.h",
"lookalikes/safety_tips/reputation_service.cc", "lookalikes/safety_tips/reputation_service.cc",
"lookalikes/safety_tips/reputation_service.h", "lookalikes/safety_tips/reputation_service.h",
"lookalikes/safety_tips/reputation_web_contents_observer.cc", "lookalikes/safety_tips/reputation_web_contents_observer.cc",
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/lookalikes/lookalike_url_allowlist.h"
#include <string>
#include "content/public/browser/web_contents.h"
// This bit of chaos ensures that kAllowlistKey is an arbitrary but
// unique-in-the-process value (namely, its own memory address) without casts.
const void* const kAllowlistKey = &kAllowlistKey;
LookalikeUrlAllowlist::LookalikeUrlAllowlist() {}
LookalikeUrlAllowlist::~LookalikeUrlAllowlist() {}
// static
LookalikeUrlAllowlist* LookalikeUrlAllowlist::GetOrCreateAllowlist(
content::WebContents* web_contents) {
LookalikeUrlAllowlist* allowlist = static_cast<LookalikeUrlAllowlist*>(
web_contents->GetUserData(kAllowlistKey));
if (!allowlist) {
allowlist = new LookalikeUrlAllowlist;
web_contents->SetUserData(kAllowlistKey, base::WrapUnique(allowlist));
}
return allowlist;
}
bool LookalikeUrlAllowlist::IsDomainInList(const std::string& domain) {
return set_.count(domain) > 0;
}
void LookalikeUrlAllowlist::AddDomain(const std::string& domain) {
set_.insert(domain);
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_LOOKALIKES_LOOKALIKE_URL_ALLOWLIST_H_
#define CHROME_BROWSER_LOOKALIKES_LOOKALIKE_URL_ALLOWLIST_H_
#include <set>
#include <string>
#include "base/macros.h"
#include "base/supports_user_data.h"
#include "url/gurl.h"
namespace content {
class WebContents;
} // namespace content
// A short-lived allowlist for bypassing lookalike URL warnings tied to
// web_contents. Since lookalike URL interstitials only trigger when site
// engagement is low, Chrome does not need to persist interstitial bypasses
// beyond the life of the web_contents-- if the user proceeds to interact with a
// page, site engagement will go up and the allowlist will be bypassed entirely.
class LookalikeUrlAllowlist : public base::SupportsUserData::Data {
public:
LookalikeUrlAllowlist();
~LookalikeUrlAllowlist() override;
static LookalikeUrlAllowlist* GetOrCreateAllowlist(
content::WebContents* web_contents);
bool IsDomainInList(const std::string& domain);
void AddDomain(const std::string& domain);
private:
std::set<std::string> set_;
DISALLOW_COPY_AND_ASSIGN(LookalikeUrlAllowlist);
};
#endif // CHROME_BROWSER_LOOKALIKES_LOOKALIKE_URL_ALLOWLIST_H_
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
#include <utility> #include <utility>
#include "chrome/browser/browser_process.h" #include "chrome/browser/browser_process.h"
#include "chrome/browser/lookalikes/lookalike_url_allowlist.h" #include "chrome/browser/lookalikes/lookalike_url_tab_storage.h"
#include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile.h"
#include "chrome/common/url_constants.h" #include "chrome/common/url_constants.h"
#include "components/security_interstitials/core/metrics_helper.h" #include "components/security_interstitials/core/metrics_helper.h"
...@@ -54,8 +54,7 @@ void LookalikeUrlControllerClient::GoBack() { ...@@ -54,8 +54,7 @@ void LookalikeUrlControllerClient::GoBack() {
} }
void LookalikeUrlControllerClient::Proceed() { void LookalikeUrlControllerClient::Proceed() {
LookalikeUrlAllowlist* allowlist = LookalikeUrlTabStorage::GetOrCreate(web_contents_)
LookalikeUrlAllowlist::GetOrCreateAllowlist(web_contents_); ->AllowDomain(request_url_.host());
allowlist->AddDomain(request_url_.host());
Reload(); Reload();
} }
...@@ -17,10 +17,10 @@ ...@@ -17,10 +17,10 @@
#include "base/stl_util.h" #include "base/stl_util.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "chrome/browser/engagement/site_engagement_service.h" #include "chrome/browser/engagement/site_engagement_service.h"
#include "chrome/browser/lookalikes/lookalike_url_allowlist.h"
#include "chrome/browser/lookalikes/lookalike_url_controller_client.h" #include "chrome/browser/lookalikes/lookalike_url_controller_client.h"
#include "chrome/browser/lookalikes/lookalike_url_interstitial_page.h" #include "chrome/browser/lookalikes/lookalike_url_interstitial_page.h"
#include "chrome/browser/lookalikes/lookalike_url_service.h" #include "chrome/browser/lookalikes/lookalike_url_service.h"
#include "chrome/browser/lookalikes/lookalike_url_tab_storage.h"
#include "chrome/browser/prerender/prerender_contents.h" #include "chrome/browser/prerender/prerender_contents.h"
#include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_features.h" #include "chrome/common/chrome_features.h"
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
#include "components/url_formatter/spoof_checks/top_domains/top500_domains.h" #include "components/url_formatter/spoof_checks/top_domains/top500_domains.h"
#include "components/url_formatter/spoof_checks/top_domains/top_domain_util.h" #include "components/url_formatter/spoof_checks/top_domains/top_domain_util.h"
#include "content/public/browser/navigation_handle.h" #include "content/public/browser/navigation_handle.h"
#include "third_party/blink/public/mojom/referrer.mojom.h"
namespace { namespace {
...@@ -155,6 +156,14 @@ std::string GetSimilarDomainFromEngagedSites( ...@@ -155,6 +156,14 @@ std::string GetSimilarDomainFromEngagedSites(
return std::string(); return std::string();
} }
// Returns true if |current_url| is at the end of the redirect chain
// stored in |stored_redirect_chain|.
bool IsInterstitialReload(const GURL& current_url,
const std::vector<GURL>& stored_redirect_chain) {
return stored_redirect_chain.size() > 1 &&
stored_redirect_chain[stored_redirect_chain.size() - 1] == current_url;
}
} // namespace } // namespace
namespace lookalikes { namespace lookalikes {
...@@ -243,17 +252,50 @@ ThrottleCheckResult LookalikeUrlNavigationThrottle::HandleThrottleRequest( ...@@ -243,17 +252,50 @@ ThrottleCheckResult LookalikeUrlNavigationThrottle::HandleThrottleRequest(
return content::NavigationThrottle::PROCEED; return content::NavigationThrottle::PROCEED;
} }
// Get stored interstitial parameters early. By doing so, we ensure that a
// navigation to an irrelevant (for this interstitial's purposes) URL such as
// chrome://settings while the lookalike interstitial is being shown clears
// the stored state:
// 1. User navigates to lookalike.tld which redirects to site.tld.
// 2. Interstitial shown.
// 3. User navigates to chrome://settings.
// If, after this, the user somehow ends up on site.tld with a reload (e.g.
// with ReloadType::ORIGINAL_REQUEST_URL), this will correctly not show an
// interstitial.
LookalikeUrlTabStorage* tab_storage =
LookalikeUrlTabStorage::GetOrCreate(handle->GetWebContents());
const LookalikeUrlTabStorage::InterstitialParams interstitial_params =
tab_storage->GetInterstitialParams();
tab_storage->ClearInterstitialParams();
if (!url.SchemeIsHTTPOrHTTPS()) { if (!url.SchemeIsHTTPOrHTTPS()) {
return content::NavigationThrottle::PROCEED; return content::NavigationThrottle::PROCEED;
} }
// If the URL is in the allowlist, don't show any warning. // If the URL is in the allowlist, don't show any warning.
LookalikeUrlAllowlist* allowlist = if (tab_storage->IsDomainAllowed(url.host())) {
LookalikeUrlAllowlist::GetOrCreateAllowlist(handle->GetWebContents());
if (allowlist->IsDomainInList(url.host())) {
return content::NavigationThrottle::PROCEED; return content::NavigationThrottle::PROCEED;
} }
// If this is a reload and if the current URL is the last URL of the stored
// redirect chain, the interstitial was probably reloaded. Stop the reload and
// navigate back to the original lookalike URL so that the whole throttle is
// exercised again.
if (handle->GetReloadType() != content::ReloadType::NONE &&
IsInterstitialReload(url, interstitial_params.redirect_chain)) {
CHECK(interstitial_params.url.SchemeIsHTTPOrHTTPS());
// See
// https://groups.google.com/a/chromium.org/forum/#!topic/chromium-dev/plIZV3Rkzok
// for why this is OK. Assume interstitial reloads are always browser
// initiated.
navigation_handle()->GetWebContents()->OpenURL(content::OpenURLParams(
interstitial_params.url, interstitial_params.referrer,
WindowOpenDisposition::CURRENT_TAB,
ui::PageTransition::PAGE_TRANSITION_RELOAD,
false /* is_renderer_initiated */));
return content::NavigationThrottle::CANCEL_AND_IGNORE;
}
const DomainInfo navigated_domain = GetDomainInfo(url); const DomainInfo navigated_domain = GetDomainInfo(url);
if (navigated_domain.domain_and_registry.empty() || if (navigated_domain.domain_and_registry.empty() ||
IsTopDomain(navigated_domain)) { IsTopDomain(navigated_domain)) {
...@@ -326,6 +368,16 @@ ThrottleCheckResult LookalikeUrlNavigationThrottle::ShowInterstitial( ...@@ -326,6 +368,16 @@ ThrottleCheckResult LookalikeUrlNavigationThrottle::ShowInterstitial(
security_interstitials::SecurityInterstitialTabHelper::AssociateBlockingPage( security_interstitials::SecurityInterstitialTabHelper::AssociateBlockingPage(
web_contents, handle->GetNavigationId(), std::move(blocking_page)); web_contents, handle->GetNavigationId(), std::move(blocking_page));
// Store interstitial parameters in per-tab storage. Reloading the
// interstitial once it's shown navigates to the final URL in the original
// redirect chain. It also loses the original redirect chain. By storing these
// parameters, we can check if the next navigation is a reload and act
// accordingly.
content::Referrer referrer(handle->GetReferrer().url,
handle->GetReferrer().policy);
LookalikeUrlTabStorage::GetOrCreate(handle->GetWebContents())
->OnLookalikeInterstitialShown(url, referrer, handle->GetRedirectChain());
return ThrottleCheckResult(content::NavigationThrottle::CANCEL, return ThrottleCheckResult(content::NavigationThrottle::CANCEL,
net::ERR_BLOCKED_BY_CLIENT, error_page_contents); net::ERR_BLOCKED_BY_CLIENT, error_page_contents);
} }
......
...@@ -590,7 +590,7 @@ IN_PROC_BROWSER_TEST_P(LookalikeUrlNavigationThrottleBrowserTest, ...@@ -590,7 +590,7 @@ IN_PROC_BROWSER_TEST_P(LookalikeUrlNavigationThrottleBrowserTest,
CheckNoUkm(); CheckNoUkm();
} }
// Test that the heuristics are not triggered even with net errors. // Test that the heuristics are not triggered with net errors.
IN_PROC_BROWSER_TEST_P(LookalikeUrlNavigationThrottleBrowserTest, IN_PROC_BROWSER_TEST_P(LookalikeUrlNavigationThrottleBrowserTest,
NetError_SiteEngagement_Interstitial) { NetError_SiteEngagement_Interstitial) {
// Create a test server that returns invalid responses. // Create a test server that returns invalid responses.
...@@ -1021,25 +1021,33 @@ IN_PROC_BROWSER_TEST_F(LookalikeUrlInterstitialPageBrowserTest, ...@@ -1021,25 +1021,33 @@ IN_PROC_BROWSER_TEST_F(LookalikeUrlInterstitialPageBrowserTest,
LoadAndCheckInterstitialAt(browser(), kNavigatedUrl); LoadAndCheckInterstitialAt(browser(), kNavigatedUrl);
} }
// Verify reloading the page results in dismissing an interstitial. // Verify reloading the page does not result in dismissing an interstitial.
// Regression test for crbug/941886. // Regression test for crbug/941886.
IN_PROC_BROWSER_TEST_F(LookalikeUrlInterstitialPageBrowserTest, IN_PROC_BROWSER_TEST_F(LookalikeUrlInterstitialPageBrowserTest,
RefreshDoesntDismiss) { RefreshDoesntDismiss) {
// Verify it works when the lookalike domain is the first in the chain // Verify it works when the lookalike domain is the first in the chain.
const GURL kNavigatedUrl = const GURL kNavigatedUrl =
GetLongRedirect("googlé.com", "example.net", "example.com"); GetLongRedirect("googlé.com", "example.net", "example.com");
SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
LoadAndCheckInterstitialAt(browser(), kNavigatedUrl);
content::WebContents* web_contents = content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents(); browser()->tab_strip_model()->GetActiveWebContents();
SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement); // Reload the interstitial twice. Should still work.
LoadAndCheckInterstitialAt(browser(), kNavigatedUrl); for (size_t i = 0; i < 2; i++) {
content::TestNavigationObserver navigation_observer(web_contents);
chrome::Reload(browser(), WindowOpenDisposition::CURRENT_TAB);
navigation_observer.Wait();
content::TestNavigationObserver navigation_observer(web_contents); EXPECT_EQ(LookalikeUrlInterstitialPage::kTypeForTesting,
chrome::Reload(browser(), WindowOpenDisposition::CURRENT_TAB); GetInterstitialType(web_contents));
navigation_observer.Wait(); EXPECT_FALSE(IsUrlShowing(browser()));
}
EXPECT_EQ(nullptr, GetInterstitialType(web_contents)); // Go to the affected site directly. This should not result in an
EXPECT_TRUE(IsUrlShowing(browser())); // interstitial.
EXPECT_EQ(GetURL("example.com"), web_contents->GetURL()); TestInterstitialNotShown(browser(),
embedded_test_server()->GetURL("example.net", "/"));
} }
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/lookalikes/lookalike_url_tab_storage.h"
#include <string>
#include "content/public/browser/web_contents.h"
// This bit of chaos ensures that kAllowlistKey is an arbitrary but
// unique-in-the-process value (namely, its own memory address) without casts.
const void* const kAllowlistKey = &kAllowlistKey;
LookalikeUrlTabStorage::InterstitialParams::InterstitialParams() = default;
LookalikeUrlTabStorage::InterstitialParams::~InterstitialParams() = default;
LookalikeUrlTabStorage::InterstitialParams::InterstitialParams(
const InterstitialParams& other) = default;
LookalikeUrlTabStorage::LookalikeUrlTabStorage() = default;
LookalikeUrlTabStorage::~LookalikeUrlTabStorage() = default;
// static
LookalikeUrlTabStorage* LookalikeUrlTabStorage::GetOrCreate(
content::WebContents* web_contents) {
LookalikeUrlTabStorage* storage = static_cast<LookalikeUrlTabStorage*>(
web_contents->GetUserData(kAllowlistKey));
if (!storage) {
storage = new LookalikeUrlTabStorage;
web_contents->SetUserData(kAllowlistKey, base::WrapUnique(storage));
}
return storage;
}
bool LookalikeUrlTabStorage::IsDomainAllowed(const std::string& domain) {
return allowed_domains_.count(domain) > 0;
}
void LookalikeUrlTabStorage::AllowDomain(const std::string& domain) {
allowed_domains_.insert(domain);
}
void LookalikeUrlTabStorage::OnLookalikeInterstitialShown(
const GURL& url,
const content::Referrer& referrer,
const std::vector<GURL>& redirect_chain) {
interstitial_params_.url = url;
interstitial_params_.referrer = referrer;
interstitial_params_.redirect_chain = redirect_chain;
}
void LookalikeUrlTabStorage::ClearInterstitialParams() {
interstitial_params_.url = GURL();
interstitial_params_.referrer = content::Referrer();
interstitial_params_.redirect_chain.clear();
}
LookalikeUrlTabStorage::InterstitialParams
LookalikeUrlTabStorage::GetInterstitialParams() const {
return interstitial_params_;
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_LOOKALIKES_LOOKALIKE_URL_TAB_STORAGE_H_
#define CHROME_BROWSER_LOOKALIKES_LOOKALIKE_URL_TAB_STORAGE_H_
#include <set>
#include <string>
#include <vector>
#include "base/macros.h"
#include "base/supports_user_data.h"
#include "content/public/browser/page_navigator.h"
#include "content/public/common/referrer.h"
#include "url/gurl.h"
namespace content {
class WebContents;
} // namespace content
// A short-lived, per-tab storage for lookalike interstitials.
// Contains an allowlist for bypassing lookalike URL warnings tied to
// web_contents and extra URL parameters for handling interstitial reloads.
// Since lookalike URL interstitials only trigger when site
// engagement is low, Chrome does not need to persist interstitial bypasses
// beyond the life of the web_contents-- if the user proceeds to interact with a
// page, site engagement will go up and the allowlist will be bypassed entirely.
class LookalikeUrlTabStorage : public base::SupportsUserData::Data {
public:
struct InterstitialParams {
// We store a limited amount of information here. This might not be
// sufficient to construct the original navigation in some edge cases (e.g.
// POST'd to the lookalike URL, which then redirected). However, the
// original navigation will be blocked with an interstitial, so this is an
// acceptable compromise.
GURL url;
std::vector<GURL> redirect_chain;
content::Referrer referrer;
InterstitialParams();
InterstitialParams(const InterstitialParams& other);
~InterstitialParams();
};
LookalikeUrlTabStorage();
~LookalikeUrlTabStorage() override;
static LookalikeUrlTabStorage* GetOrCreate(
content::WebContents* web_contents);
bool IsDomainAllowed(const std::string& domain);
void AllowDomain(const std::string& domain);
// Stores parameters associated with a lookalike interstitial. Must be called
// when a lookalike interstitial is shown.
void OnLookalikeInterstitialShown(const GURL& url,
const content::Referrer& referrer,
const std::vector<GURL>& redirect_chain);
// Clears stored parameters associated with a lookalike interstitial.
void ClearInterstitialParams();
// Returns currently stored parameters associated with a lookalike
// interstitial.
InterstitialParams GetInterstitialParams() const;
private:
std::set<std::string> allowed_domains_;
// Parameters associated with the currently displayed interstitial. These are
// cleared immediately on next navigation.
InterstitialParams interstitial_params_;
DISALLOW_COPY_AND_ASSIGN(LookalikeUrlTabStorage);
};
#endif // CHROME_BROWSER_LOOKALIKES_LOOKALIKE_URL_TAB_STORAGE_H_
...@@ -7,7 +7,6 @@ ...@@ -7,7 +7,6 @@
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/scoped_refptr.h" #include "base/memory/scoped_refptr.h"
#include "base/memory/singleton.h" #include "base/memory/singleton.h"
#include "chrome/browser/lookalikes/lookalike_url_allowlist.h"
#include "chrome/browser/lookalikes/lookalike_url_service.h" #include "chrome/browser/lookalikes/lookalike_url_service.h"
#include "chrome/browser/profiles/incognito_helpers.h" #include "chrome/browser/profiles/incognito_helpers.h"
#include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile.h"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment