Commit a42bdc1e authored by Joe DeBlasio's avatar Joe DeBlasio Committed by Commit Bot

[Lookalikes] Change how the lookalike interstitial handles redirects

This CL changes what domains we consider for the lookalike interstitial.
Previously, if Chrome saw a navigation that looked like
    A -> B -> C -> D
(where site A is redirecting to B, etc.), the lookalike logic would
inspect each stage, A through D, for lookalikes. It accomplished this by
implementing DidRedirectNavigation() and DidFinishNavigation(). This
added a bunch of complexity, but allowed us to stop a navigation
mid-redirect.

This CL changes that behavior to wait until the entire redirect chain
has completed, and then inspect only sites A and D. These domains are
the only ones visible to the user, and thus the only ones wherein a
lookalike URL might be effective. This change also means that we no
longer implement DidRedirectNavigation().

Bug: 1092690
Change-Id: I2cfa724b9793b60dd5200d1bf6847cc6919fa469
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2438888Reviewed-by: default avatarLivvie Lin <livvielin@chromium.org>
Reviewed-by: default avatarMustafa Emre Acer <meacer@chromium.org>
Commit-Queue: Joe DeBlasio <jdeblasio@chromium.org>
Cr-Commit-Position: refs/heads/master@{#812173}
parent c2123ce0
...@@ -26,8 +26,6 @@ ...@@ -26,8 +26,6 @@
#include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile.h"
#include "chrome/browser/reputation/reputation_service.h" #include "chrome/browser/reputation/reputation_service.h"
#include "chrome/browser/reputation/safety_tips_config.h" #include "chrome/browser/reputation/safety_tips_config.h"
#include "chrome/common/chrome_features.h"
#include "components/lookalikes/core/features.h"
#include "components/lookalikes/core/lookalike_url_ui_util.h" #include "components/lookalikes/core/lookalike_url_ui_util.h"
#include "components/lookalikes/core/lookalike_url_util.h" #include "components/lookalikes/core/lookalike_url_util.h"
#include "components/prerender/browser/prerender_contents.h" #include "components/prerender/browser/prerender_contents.h"
...@@ -50,44 +48,8 @@ bool IsInterstitialReload(const GURL& current_url, ...@@ -50,44 +48,8 @@ bool IsInterstitialReload(const GURL& current_url,
stored_redirect_chain[stored_redirect_chain.size() - 1] == current_url; stored_redirect_chain[stored_redirect_chain.size() - 1] == current_url;
} }
// Returns the index of the first URL in the redirect chain which has a
// different eTLD+1 than the initial URL. If all URLs have the same eTLD+1,
// returns 0.
size_t FindFirstCrossSiteURL(const std::vector<GURL>& redirect_chain) {
DCHECK_GE(redirect_chain.size(), 2u);
const GURL initial_url = redirect_chain[0];
const std::string initial_etld_plus_one = GetETLDPlusOne(initial_url.host());
for (size_t i = 1; i < redirect_chain.size(); i++) {
if (initial_etld_plus_one != GetETLDPlusOne(redirect_chain[i].host())) {
return i;
}
}
return 0;
}
} // namespace } // namespace
bool IsSafeRedirect(const std::string& matching_domain,
const std::vector<GURL>& redirect_chain) {
if (redirect_chain.size() < 2) {
return false;
}
const size_t first_cross_site_redirect =
FindFirstCrossSiteURL(redirect_chain);
DCHECK_GE(first_cross_site_redirect, 0u);
DCHECK_LE(first_cross_site_redirect, redirect_chain.size() - 1);
if (first_cross_site_redirect == 0) {
// All URLs in the redirect chain belong to the same eTLD+1.
return false;
}
// There is a redirect from the initial eTLD+1 to another site. In order to be
// a safe redirect, it should be to the root of |matching_domain|. This
// ignores any further redirects after |matching_domain|.
const GURL redirect_target = redirect_chain[first_cross_site_redirect];
return matching_domain == GetETLDPlusOne(redirect_target.host()) &&
redirect_target == redirect_target.GetWithEmptyPath();
}
LookalikeUrlNavigationThrottle::LookalikeUrlNavigationThrottle( LookalikeUrlNavigationThrottle::LookalikeUrlNavigationThrottle(
content::NavigationHandle* navigation_handle) content::NavigationHandle* navigation_handle)
: content::NavigationThrottle(navigation_handle), : content::NavigationThrottle(navigation_handle),
...@@ -96,9 +58,7 @@ LookalikeUrlNavigationThrottle::LookalikeUrlNavigationThrottle( ...@@ -96,9 +58,7 @@ LookalikeUrlNavigationThrottle::LookalikeUrlNavigationThrottle(
LookalikeUrlNavigationThrottle::~LookalikeUrlNavigationThrottle() {} LookalikeUrlNavigationThrottle::~LookalikeUrlNavigationThrottle() {}
ThrottleCheckResult LookalikeUrlNavigationThrottle::HandleThrottleRequest( ThrottleCheckResult LookalikeUrlNavigationThrottle::WillProcessResponse() {
const GURL& url,
bool check_safe_redirect) {
// Ignore if running unit tests. Some tests use // Ignore if running unit tests. Some tests use
// TestMockTimeTaskRunner::ScopedContext and call CreateTestWebContents() // TestMockTimeTaskRunner::ScopedContext and call CreateTestWebContents()
// which navigates and waits for throttles to complete using a RunLoop. // which navigates and waits for throttles to complete using a RunLoop.
...@@ -114,8 +74,9 @@ ThrottleCheckResult LookalikeUrlNavigationThrottle::HandleThrottleRequest( ...@@ -114,8 +74,9 @@ ThrottleCheckResult LookalikeUrlNavigationThrottle::HandleThrottleRequest(
content::NavigationHandle* handle = navigation_handle(); content::NavigationHandle* handle = navigation_handle();
// Ignore subframe and same document navigations. // Ignore errors, subframe and same document navigations.
if (!handle->IsInMainFrame() || handle->IsSameDocument()) { if (handle->GetNetErrorCode() != net::OK || !handle->IsInMainFrame() ||
handle->IsSameDocument()) {
return content::NavigationThrottle::PROCEED; return content::NavigationThrottle::PROCEED;
} }
...@@ -135,45 +96,19 @@ ThrottleCheckResult LookalikeUrlNavigationThrottle::HandleThrottleRequest( ...@@ -135,45 +96,19 @@ ThrottleCheckResult LookalikeUrlNavigationThrottle::HandleThrottleRequest(
tab_storage->GetInterstitialParams(); tab_storage->GetInterstitialParams();
tab_storage->ClearInterstitialParams(); tab_storage->ClearInterstitialParams();
if (!url.SchemeIsHTTPOrHTTPS()) {
return content::NavigationThrottle::PROCEED;
}
// Fetch the component allowlist.
const auto* proto = GetSafetyTipsRemoteConfigProto();
// When there's no proto (like at browser start), fail-safe and don't block.
if (!proto) {
return content::NavigationThrottle::PROCEED;
}
// If the URL is in the component allowlist, don't show any warning.
if (IsUrlAllowlistedBySafetyTipsComponent(proto, url.GetWithEmptyPath())) {
return content::NavigationThrottle::PROCEED;
}
// If the URL is in the local temporary allowlist, don't show any warning.
if (ReputationService::Get(profile_)->IsIgnored(url)) {
return content::NavigationThrottle::PROCEED;
}
// If the host is allowlisted by policy, don't show any warning.
if (IsAllowedByEnterprisePolicy(profile_->GetPrefs(), url)) {
return content::NavigationThrottle::PROCEED;
}
// If this is a reload and if the current URL is the last URL of the stored // 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 // redirect chain, the interstitial was probably reloaded. Stop the reload and
// navigate back to the original lookalike URL so that the whole throttle is // navigate back to the original lookalike URL so that the whole throttle is
// exercised again. // exercised again.
if (handle->GetReloadType() != content::ReloadType::NONE && if (handle->GetReloadType() != content::ReloadType::NONE &&
IsInterstitialReload(url, interstitial_params.redirect_chain)) { IsInterstitialReload(handle->GetURL(),
interstitial_params.redirect_chain)) {
CHECK(interstitial_params.url.SchemeIsHTTPOrHTTPS()); CHECK(interstitial_params.url.SchemeIsHTTPOrHTTPS());
// See // See
// https://groups.google.com/a/chromium.org/forum/#!topic/chromium-dev/plIZV3Rkzok // https://groups.google.com/a/chromium.org/forum/#!topic/chromium-dev/plIZV3Rkzok
// for why this is OK. Assume interstitial reloads are always browser // for why this is OK. Assume interstitial reloads are always browser
// initiated. // initiated.
navigation_handle()->GetWebContents()->OpenURL(content::OpenURLParams( handle->GetWebContents()->OpenURL(content::OpenURLParams(
interstitial_params.url, interstitial_params.referrer, interstitial_params.url, interstitial_params.referrer,
WindowOpenDisposition::CURRENT_TAB, WindowOpenDisposition::CURRENT_TAB,
ui::PageTransition::PAGE_TRANSITION_RELOAD, ui::PageTransition::PAGE_TRANSITION_RELOAD,
...@@ -181,47 +116,15 @@ ThrottleCheckResult LookalikeUrlNavigationThrottle::HandleThrottleRequest( ...@@ -181,47 +116,15 @@ ThrottleCheckResult LookalikeUrlNavigationThrottle::HandleThrottleRequest(
return content::NavigationThrottle::CANCEL_AND_IGNORE; return content::NavigationThrottle::CANCEL_AND_IGNORE;
} }
const DomainInfo navigated_domain = GetDomainInfo(url);
// Empty domain_and_registry happens on private domains.
if (navigated_domain.domain_and_registry.empty() ||
IsTopDomain(navigated_domain)) {
return content::NavigationThrottle::PROCEED;
}
LookalikeUrlService* service = LookalikeUrlService::Get(profile_); LookalikeUrlService* service = LookalikeUrlService::Get(profile_);
if (!use_test_profile_ && service->EngagedSitesNeedUpdating()) { if (!use_test_profile_ && service->EngagedSitesNeedUpdating()) {
service->ForceUpdateEngagedSites( service->ForceUpdateEngagedSites(
base::BindOnce(&LookalikeUrlNavigationThrottle::PerformChecksDeferred, base::BindOnce(&LookalikeUrlNavigationThrottle::PerformChecksDeferred,
weak_factory_.GetWeakPtr(), url, navigated_domain, weak_factory_.GetWeakPtr()));
check_safe_redirect));
return content::NavigationThrottle::DEFER; return content::NavigationThrottle::DEFER;
} }
return PerformChecks(url, navigated_domain, check_safe_redirect, return PerformChecks(service->GetLatestEngagedSites());
service->GetLatestEngagedSites());
}
ThrottleCheckResult LookalikeUrlNavigationThrottle::WillProcessResponse() {
if (navigation_handle()->GetNetErrorCode() != net::OK) {
return content::NavigationThrottle::PROCEED;
}
// Do not check for if the redirect was safe. That should only be done when
// the navigation is still being redirected.
return HandleThrottleRequest(navigation_handle()->GetURL(), false);
}
ThrottleCheckResult LookalikeUrlNavigationThrottle::WillRedirectRequest() {
const std::vector<GURL>& chain = navigation_handle()->GetRedirectChain();
// WillRedirectRequest is called after a redirect occurs, so the end of the
// chain is the URL that was redirected to. We need to check the preceding URL
// that caused the redirection. The final URL in the chain is checked either:
// - after the next redirection (when there is a longer chain), or
// - by WillProcessResponse (before content is rendered).
if (chain.size() < 2) {
return content::NavigationThrottle::PROCEED;
}
return HandleThrottleRequest(chain[chain.size() - 2], true);
} }
const char* LookalikeUrlNavigationThrottle::GetNameForLogging() { const char* LookalikeUrlNavigationThrottle::GetNameForLogging() {
...@@ -278,12 +181,8 @@ LookalikeUrlNavigationThrottle::MaybeCreateNavigationThrottle( ...@@ -278,12 +181,8 @@ LookalikeUrlNavigationThrottle::MaybeCreateNavigationThrottle(
} }
void LookalikeUrlNavigationThrottle::PerformChecksDeferred( void LookalikeUrlNavigationThrottle::PerformChecksDeferred(
const GURL& url,
const DomainInfo& navigated_domain,
bool check_safe_redirect,
const std::vector<DomainInfo>& engaged_sites) { const std::vector<DomainInfo>& engaged_sites) {
ThrottleCheckResult result = ThrottleCheckResult result = PerformChecks(engaged_sites);
PerformChecks(url, navigated_domain, check_safe_redirect, engaged_sites);
if (result.action() == content::NavigationThrottle::PROCEED) { if (result.action() == content::NavigationThrottle::PROCEED) {
Resume(); Resume();
...@@ -294,12 +193,102 @@ void LookalikeUrlNavigationThrottle::PerformChecksDeferred( ...@@ -294,12 +193,102 @@ void LookalikeUrlNavigationThrottle::PerformChecksDeferred(
} }
ThrottleCheckResult LookalikeUrlNavigationThrottle::PerformChecks( ThrottleCheckResult LookalikeUrlNavigationThrottle::PerformChecks(
const GURL& url,
const DomainInfo& navigated_domain,
bool check_safe_redirect,
const std::vector<DomainInfo>& engaged_sites) { const std::vector<DomainInfo>& engaged_sites) {
std::string matched_domain; DCHECK_EQ(
LookalikeUrlMatchType match_type; navigation_handle()
->GetRedirectChain()[navigation_handle()->GetRedirectChain().size() -
1],
navigation_handle()->GetURL());
// Check for two lookalikes -- at the beginning and end of the redirect chain.
const GURL& first_url = navigation_handle()->GetRedirectChain()[0];
LookalikeUrlMatchType first_match_type;
GURL first_suggested_url;
bool first_is_lookalike = IsLookalikeUrl(
first_url, engaged_sites, &first_match_type, &first_suggested_url);
const GURL& last_url = navigation_handle()->GetURL();
LookalikeUrlMatchType last_match_type;
GURL last_suggested_url;
// If first_url == last_url, then don't check a second time. This saves time,
// and avoids clouding metrics.
bool last_is_lookalike =
first_url != last_url &&
IsLookalikeUrl(last_url, engaged_sites, &last_match_type,
&last_suggested_url);
if (!first_is_lookalike && !last_is_lookalike) {
return content::NavigationThrottle::PROCEED;
}
// If the first URL is a lookalike, but we ended up on the suggested site
// anyway, don't warn.
if (first_is_lookalike &&
last_url.DomainIs(GetETLDPlusOne(first_suggested_url.host()))) {
first_is_lookalike = false;
}
// source_id corresponds to last_url, even when first_url is what triggered.
// TODO(crbug.com/1133598): disambiguate first_- vs. last_urls.
ukm::SourceId source_id = ukm::ConvertToSourceId(
navigation_handle()->GetNavigationId(), ukm::SourceIdType::NAVIGATION_ID);
if (first_is_lookalike &&
ShouldBlockLookalikeUrlNavigation(first_match_type)) {
return ShowInterstitial(first_suggested_url, first_url, source_id,
first_match_type);
}
if (last_is_lookalike && ShouldBlockLookalikeUrlNavigation(last_match_type)) {
return ShowInterstitial(last_suggested_url, last_url, source_id,
last_match_type);
}
// Interstitial normally records UKM, but still record when it's not shown.
RecordUkmForLookalikeUrlBlockingPage(
source_id, first_is_lookalike ? first_match_type : last_match_type,
LookalikeUrlBlockingPageUserAction::kInterstitialNotShown);
return content::NavigationThrottle::PROCEED;
}
bool LookalikeUrlNavigationThrottle::IsLookalikeUrl(
const GURL& url,
const std::vector<DomainInfo>& engaged_sites,
LookalikeUrlMatchType* match_type,
GURL* suggested_url) {
if (!url.SchemeIsHTTPOrHTTPS()) {
return false;
}
const DomainInfo navigated_domain = GetDomainInfo(url);
// Empty domain_and_registry happens on private domains.
if (navigated_domain.domain_and_registry.empty() ||
IsTopDomain(navigated_domain)) {
return content::NavigationThrottle::PROCEED;
}
// Fetch the component allowlist.
const auto* proto = GetSafetyTipsRemoteConfigProto();
// When there's no proto (like at browser start), fail-safe and don't block.
if (!proto) {
return false;
}
// If the URL is in the component allowlist, don't show any warning.
if (IsUrlAllowlistedBySafetyTipsComponent(proto, url.GetWithEmptyPath())) {
return false;
}
// If the URL is in the local temporary allowlist, don't show any warning.
if (ReputationService::Get(profile_)->IsIgnored(url)) {
return false;
}
// If the host is allowlisted by policy, don't show any warning.
if (IsAllowedByEnterprisePolicy(profile_->GetPrefs(), url)) {
return false;
}
// Ensure that this URL is not already engaged. We can't use the synchronous // Ensure that this URL is not already engaged. We can't use the synchronous
// SiteEngagementService::IsEngagementAtLeast as it has side effects. We check // SiteEngagementService::IsEngagementAtLeast as it has side effects. We check
...@@ -314,69 +303,50 @@ ThrottleCheckResult LookalikeUrlNavigationThrottle::PerformChecks( ...@@ -314,69 +303,50 @@ ThrottleCheckResult LookalikeUrlNavigationThrottle::PerformChecks(
engaged_domain.domain_and_registry); engaged_domain.domain_and_registry);
}); });
if (already_engaged != engaged_sites.end()) { if (already_engaged != engaged_sites.end()) {
return content::NavigationThrottle::PROCEED; return false;
} }
ukm::SourceId source_id = ukm::ConvertToSourceId(
navigation_handle()->GetNavigationId(), ukm::SourceIdType::NAVIGATION_ID);
auto* config = GetSafetyTipsRemoteConfigProto();
const LookalikeTargetAllowlistChecker in_target_allowlist = const LookalikeTargetAllowlistChecker in_target_allowlist =
base::BindRepeating(&IsTargetHostAllowlistedBySafetyTipsComponent, base::BindRepeating(&IsTargetHostAllowlistedBySafetyTipsComponent, proto);
config); std::string matched_domain;
if (GetMatchingDomain(navigated_domain, engaged_sites, in_target_allowlist, if (GetMatchingDomain(navigated_domain, engaged_sites, in_target_allowlist,
&matched_domain, &match_type)) { &matched_domain, match_type)) {
DCHECK(!matched_domain.empty()); DCHECK(!matched_domain.empty());
RecordUMAFromMatchType(match_type); RecordUMAFromMatchType(*match_type);
if (check_safe_redirect && // matched_domain can be a top domain or an engaged domain. Simply use its
IsSafeRedirect(matched_domain, // eTLD+1 as the suggested domain.
navigation_handle()->GetRedirectChain())) { // 1. If matched_domain is a top domain: Top domain list already contains
return content::NavigationThrottle::PROCEED; // eTLD+1s only so this works well.
} // 2. If matched_domain is an engaged domain and is not an eTLD+1, don't
// suggest it. Otherwise, navigating to googlé.com and having engaged with
if (ShouldBlockLookalikeUrlNavigation(match_type, navigated_domain)) { // docs.google.com would suggest docs.google.com.
// matched_domain can be a top domain or an engaged domain. Simply use its //
// eTLD+1 as the suggested domain. // When the navigated and matched domains are not eTLD+1s (e.g.
// 1. If matched_domain is a top domain: Top domain list already contains // docs.googlé.com and docs.google.com), this will suggest google.com
// eTLD+1s only so this works well. // instead of docs.google.com. This is less than ideal, but has two
// 2. If matched_domain is an engaged domain and is not an eTLD+1, don't // benefits:
// suggest it. Otherwise, navigating to googlé.com and having engaged with // - Simpler code
// docs.google.com would suggest docs.google.com. // - Fewer suggestions to non-existent domains. E.g. When the navigated
// // domain is nonexistent.googlé.com and the matched domain is
// When the navigated and matched domains are not eTLD+1s (e.g. // docs.google.com, we will suggest google.com instead of
// docs.googlé.com and docs.google.com), this will suggest google.com // nonexistent.google.com.
// instead of docs.google.com. This is less than ideal, but has two std::string suggested_domain = GetETLDPlusOne(matched_domain);
// benefits: DCHECK(!suggested_domain.empty());
// - Simpler code // Drop everything but the parts of the origin.
// - Fewer suggestions to non-existent domains. E.g. When the navigated GURL::Replacements replace_host;
// domain is nonexistent.googlé.com and the matched domain is replace_host.SetHostStr(suggested_domain);
// docs.google.com, we will suggest google.com instead of *suggested_url = url.ReplaceComponents(replace_host).GetWithEmptyPath();
// nonexistent.google.com. return true;
const std::string suggested_domain = GetETLDPlusOne(matched_domain);
DCHECK(!suggested_domain.empty());
// Drop everything but the parts of the origin.
GURL::Replacements replace_host;
replace_host.SetHostStr(suggested_domain);
const GURL suggested_url =
url.ReplaceComponents(replace_host).GetWithEmptyPath();
return ShowInterstitial(suggested_url, url, source_id, match_type);
}
// Interstitial normally records UKM, but still record when it's not shown.
RecordUkmForLookalikeUrlBlockingPage(
source_id, match_type,
LookalikeUrlBlockingPageUserAction::kInterstitialNotShown);
return content::NavigationThrottle::PROCEED;
} }
if (base::FeatureList::IsEnabled( if (ShouldBlockBySpoofCheckResult(navigated_domain)) {
lookalikes::features::kLookalikeInterstitialForPunycode) && *match_type = LookalikeUrlMatchType::kFailedSpoofChecks;
ShouldBlockBySpoofCheckResult(navigated_domain)) { *suggested_url = GURL();
match_type = LookalikeUrlMatchType::kFailedSpoofChecks; RecordUMAFromMatchType(*match_type);
RecordUMAFromMatchType(match_type); return true;
return ShowInterstitial(GURL(), url, source_id, match_type);
} }
return content::NavigationThrottle::PROCEED; return false;
} }
...@@ -45,7 +45,6 @@ class LookalikeUrlNavigationThrottle : public content::NavigationThrottle { ...@@ -45,7 +45,6 @@ class LookalikeUrlNavigationThrottle : public content::NavigationThrottle {
// content::NavigationThrottle: // content::NavigationThrottle:
ThrottleCheckResult WillProcessResponse() override; ThrottleCheckResult WillProcessResponse() override;
ThrottleCheckResult WillRedirectRequest() override;
const char* GetNameForLogging() override; const char* GetNameForLogging() override;
static std::unique_ptr<LookalikeUrlNavigationThrottle> static std::unique_ptr<LookalikeUrlNavigationThrottle>
...@@ -56,24 +55,20 @@ class LookalikeUrlNavigationThrottle : public content::NavigationThrottle { ...@@ -56,24 +55,20 @@ class LookalikeUrlNavigationThrottle : public content::NavigationThrottle {
void SetUseTestProfileForTesting() { use_test_profile_ = true; } void SetUseTestProfileForTesting() { use_test_profile_ = true; }
private: private:
// Checks whether the navigation to |url| can proceed. If
// |check_safe_redirect| is true, will check if a safe redirect led to |url|.
ThrottleCheckResult HandleThrottleRequest(const GURL& url,
bool check_safe_redirect);
// Performs synchronous top domain and engaged site checks on the navigated // Performs synchronous top domain and engaged site checks on the navigated
// |url|. Uses |engaged_sites| for the engaged site checks. // and redirected urls. Uses |engaged_sites| for the engaged site checks.
ThrottleCheckResult PerformChecks( ThrottleCheckResult PerformChecks(
const GURL& url,
const DomainInfo& navigated_domain,
bool check_safe_redirect,
const std::vector<DomainInfo>& engaged_sites); const std::vector<DomainInfo>& engaged_sites);
// A void-returning variant, only used with deferred throttle results. // A void-returning variant, only used with deferred throttle results.
void PerformChecksDeferred(const GURL& url, void PerformChecksDeferred(const std::vector<DomainInfo>& engaged_sites);
const DomainInfo& navigated_domain,
bool check_safe_redirect, // Returns whether |url| is a lookalike, setting |match_type| and
const std::vector<DomainInfo>& engaged_sites); // |suggested_url| appropriately. Used in PerformChecks() on a per-URL basis.
bool IsLookalikeUrl(const GURL& url,
const std::vector<DomainInfo>& engaged_sites,
LookalikeUrlMatchType* match_type,
GURL* suggested_url);
ThrottleCheckResult ShowInterstitial(const GURL& safe_domain, ThrottleCheckResult ShowInterstitial(const GURL& safe_domain,
const GURL& url, const GURL& url,
......
...@@ -579,14 +579,13 @@ IN_PROC_BROWSER_TEST_P(LookalikeUrlNavigationThrottleBrowserTest, ...@@ -579,14 +579,13 @@ IN_PROC_BROWSER_TEST_P(LookalikeUrlNavigationThrottleBrowserTest,
if (!punycode_interstitial_enabled()) { if (!punycode_interstitial_enabled()) {
TestInterstitialNotShown(browser(), kNavigatedUrl); TestInterstitialNotShown(browser(), kNavigatedUrl);
CheckNoUkm();
} else { } else {
TestPunycodeInterstitialShown( TestPunycodeInterstitialShown(
browser(), kNavigatedUrl, browser(), kNavigatedUrl,
NavigationSuggestionEvent::kFailedSpoofChecks); NavigationSuggestionEvent::kFailedSpoofChecks);
CheckUkm({kNavigatedUrl}, "MatchType",
LookalikeUrlMatchType::kFailedSpoofChecks);
} }
CheckUkm({kNavigatedUrl}, "MatchType",
LookalikeUrlMatchType::kFailedSpoofChecks);
} }
// The navigated domain will fall back to punycode because it fails spoof checks // The navigated domain will fall back to punycode because it fails spoof checks
...@@ -890,20 +889,18 @@ IN_PROC_BROWSER_TEST_P(LookalikeUrlNavigationThrottleBrowserTest, ...@@ -890,20 +889,18 @@ IN_PROC_BROWSER_TEST_P(LookalikeUrlNavigationThrottleBrowserTest,
// The site redirects to the matched site, but the redirect chain has more than // The site redirects to the matched site, but the redirect chain has more than
// two redirects. // two redirects.
// TODO(meacer): Consider allowing this case.
IN_PROC_BROWSER_TEST_P(LookalikeUrlNavigationThrottleBrowserTest, IN_PROC_BROWSER_TEST_P(LookalikeUrlNavigationThrottleBrowserTest,
Idn_SiteEngagement_UnsafeRedirect) { Idn_SiteEngagement_MidRedirectSpoofsIgnored) {
const GURL kExpectedSuggestedUrl = GetURLWithoutPath("site1.com"); const GURL kFinalUrl = GetURLWithoutPath("site1.com");
const GURL kMidUrl = embedded_test_server()->GetURL( const GURL kMidUrl = embedded_test_server()->GetURL(
"sité1.com", "/server-redirect?" + kExpectedSuggestedUrl.spec()); "sité1.com", "/server-redirect?" + kFinalUrl.spec());
const GURL kNavigatedUrl = embedded_test_server()->GetURL( const GURL kNavigatedUrl = embedded_test_server()->GetURL(
"other-site.test", "/server-redirect?" + kMidUrl.spec()); "other-site.test", "/server-redirect?" + kMidUrl.spec());
SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement); SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
SetEngagementScore(browser(), kExpectedSuggestedUrl, kHighEngagement); SetEngagementScore(browser(), kFinalUrl, kHighEngagement);
TestMetricsRecordedAndInterstitialShown( TestInterstitialNotShown(browser(), kNavigatedUrl);
browser(), kNavigatedUrl, kExpectedSuggestedUrl, CheckNoUkm();
NavigationSuggestionEvent::kMatchSiteEngagement);
} }
// The site is allowed by the component updater. // The site is allowed by the component updater.
...@@ -1126,17 +1123,17 @@ IN_PROC_BROWSER_TEST_P(LookalikeUrlNavigationThrottleBrowserTest, ...@@ -1126,17 +1123,17 @@ IN_PROC_BROWSER_TEST_P(LookalikeUrlNavigationThrottleBrowserTest,
NavigateToURLSync(browser(), GetURL("example.com")); NavigateToURLSync(browser(), GetURL("example.com"));
{ {
// ...or when it's later in the chain // ...but not when it's in the middle of the chain
const GURL kNavigatedUrl = const GURL kNavigatedUrl =
GetLongRedirect("example.net", "googlé.com", "example.com"); GetLongRedirect("example.net", "googlé.com", "example.com");
SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement); SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
LoadAndCheckInterstitialAt(browser(), kNavigatedUrl); TestInterstitialNotShown(browser(), kNavigatedUrl);
} }
NavigateToURLSync(browser(), GetURL("example.com")); NavigateToURLSync(browser(), GetURL("example.com"));
{ {
// ...or when it's last in the chain // ...but definitely when it's last in the chain.
const GURL kNavigatedUrl = const GURL kNavigatedUrl =
GetLongRedirect("example.net", "example.com", "googlé.com"); GetLongRedirect("example.net", "example.com", "googlé.com");
SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement); SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
......
...@@ -16,74 +16,6 @@ ...@@ -16,74 +16,6 @@
namespace lookalikes { namespace lookalikes {
// These redirects are safe:
// - http[s]://sité.test -> http[s]://site.test
// - http[s]://sité.test/path -> http[s]://site.test
// - http[s]://subdomain.sité.test -> http[s]://site.test
// - http[s]://random.test -> http[s]://sité.test -> http[s]://site.test
// - http://sité.test/path -> https://sité.test/path -> https://site.test ->
// <any_url>
// - "subdomain" on either side.
//
// These are not safe:
// - http[s]://[subdomain.]sité.test -> http[s]://[subdomain.]site.test/path
// because the redirected URL has a path.
TEST(LookalikeUrlNavigationThrottleTest, IsSafeRedirect) {
EXPECT_TRUE(IsSafeRedirect(
"example.com", {GURL("http://éxample.com"), GURL("http://example.com")}));
EXPECT_TRUE(IsSafeRedirect(
"example.com", {GURL("http://éxample.com"), GURL("http://example.com")}));
EXPECT_TRUE(IsSafeRedirect(
"example.com",
{GURL("http://éxample.com"), GURL("http://subdomain.example.com")}));
EXPECT_TRUE(IsSafeRedirect(
"example.com", {GURL("http://éxample.com"), GURL("http://example.com"),
GURL("https://example.com")}));
// Original site redirects to HTTPS.
EXPECT_TRUE(IsSafeRedirect(
"example.com", {GURL("http://éxample.com"), GURL("https://éxample.com"),
GURL("https://example.com")}));
// Original site redirects to HTTPS which redirects to HTTP which redirects
// back to HTTPS of the non-IDN version.
EXPECT_TRUE(IsSafeRedirect(
"example.com",
{GURL("http://éxample.com/redir1"), GURL("https://éxample.com/redir1"),
GURL("http://éxample.com/redir2"), GURL("https://example.com/")}));
// Same as above, but there is another redirect at the end of the chain.
EXPECT_TRUE(IsSafeRedirect(
"example.com",
{GURL("http://éxample.com/redir1"), GURL("https://éxample.com/redir1"),
GURL("http://éxample.com/redir2"), GURL("https://example.com/"),
GURL("https://totallydifferentsite.com/somepath")}));
// Not a redirect, the chain is too short.
EXPECT_FALSE(IsSafeRedirect("example.com", {GURL("http://éxample.com")}));
// Not safe: Redirected site is not the same as the matched site.
EXPECT_FALSE(IsSafeRedirect("example.com", {GURL("http://éxample.com"),
GURL("http://other-site.com")}));
// Not safe: Initial URL doesn't redirect to the root of the suggested domain.
EXPECT_FALSE(IsSafeRedirect(
"example.com",
{GURL("http://éxample.com"), GURL("http://example.com/path")}));
// Not safe: The first redirect away from éxample.com is not to the matching
// non-IDN site.
EXPECT_FALSE(IsSafeRedirect("example.com", {GURL("http://éxample.com"),
GURL("http://intermediate.com"),
GURL("http://example.com")}));
// Not safe: The redirect stays unsafe from éxample.com to éxample.com.
EXPECT_FALSE(IsSafeRedirect(
"example.com", {GURL("http://éxample.com"), GURL("http://éxample.com")}));
// Not safe: Same, but to a path on the bad domain
EXPECT_FALSE(IsSafeRedirect(
"example.com",
{GURL("http://éxample.com"), GURL("http://éxample.com/path")}));
// Not safe: Same, but with an intermediary domain.
EXPECT_FALSE(IsSafeRedirect("example.com", {GURL("http://éxample.com/path"),
GURL("http://intermediate.com/p"),
GURL("http://éxample.com/dir")}));
}
class LookalikeThrottleTest : public ChromeRenderViewHostTestHarness {}; class LookalikeThrottleTest : public ChromeRenderViewHostTestHarness {};
// Tests that spoofy hostnames are properly handled in the throttle. // Tests that spoofy hostnames are properly handled in the throttle.
...@@ -146,6 +78,7 @@ TEST_F(LookalikeThrottleTest, SpoofsBlocked) { ...@@ -146,6 +78,7 @@ TEST_F(LookalikeThrottleTest, SpoofsBlocked) {
GURL url(std::string("http://") + test_case.hostname); GURL url(std::string("http://") + test_case.hostname);
content::MockNavigationHandle handle(url, main_rfh()); content::MockNavigationHandle handle(url, main_rfh());
handle.set_redirect_chain({url});
handle.set_page_transition(ui::PAGE_TRANSITION_TYPED); handle.set_page_transition(ui::PAGE_TRANSITION_TYPED);
auto throttle = auto throttle =
......
...@@ -69,7 +69,7 @@ bool ShouldTriggerSafetyTipFromLookalike( ...@@ -69,7 +69,7 @@ bool ShouldTriggerSafetyTipFromLookalike(
} }
// If we're already displaying an interstitial, don't warn again. // If we're already displaying an interstitial, don't warn again.
if (ShouldBlockLookalikeUrlNavigation(match_type, navigated_domain)) { if (ShouldBlockLookalikeUrlNavigation(match_type)) {
return false; return false;
} }
......
...@@ -452,8 +452,7 @@ bool IsTopDomain(const DomainInfo& domain_info) { ...@@ -452,8 +452,7 @@ bool IsTopDomain(const DomainInfo& domain_info) {
return false; return false;
} }
bool ShouldBlockLookalikeUrlNavigation(LookalikeUrlMatchType match_type, bool ShouldBlockLookalikeUrlNavigation(LookalikeUrlMatchType match_type) {
const DomainInfo& navigated_domain) {
if (match_type == LookalikeUrlMatchType::kSiteEngagement) { if (match_type == LookalikeUrlMatchType::kSiteEngagement) {
return true; return true;
} }
...@@ -462,6 +461,11 @@ bool ShouldBlockLookalikeUrlNavigation(LookalikeUrlMatchType match_type, ...@@ -462,6 +461,11 @@ bool ShouldBlockLookalikeUrlNavigation(LookalikeUrlMatchType match_type,
lookalikes::features::kDetectTargetEmbeddingLookalikes)) { lookalikes::features::kDetectTargetEmbeddingLookalikes)) {
return true; return true;
} }
if (match_type == LookalikeUrlMatchType::kFailedSpoofChecks &&
base::FeatureList::IsEnabled(
lookalikes::features::kLookalikeInterstitialForPunycode)) {
return true;
}
return match_type == LookalikeUrlMatchType::kSkeletonMatchTop500; return match_type == LookalikeUrlMatchType::kSkeletonMatchTop500;
} }
......
...@@ -152,8 +152,7 @@ bool IsTopDomain(const DomainInfo& domain_info); ...@@ -152,8 +152,7 @@ bool IsTopDomain(const DomainInfo& domain_info);
std::string GetETLDPlusOne(const std::string& hostname); std::string GetETLDPlusOne(const std::string& hostname);
// Returns true if a lookalike interstitial should be shown. // Returns true if a lookalike interstitial should be shown.
bool ShouldBlockLookalikeUrlNavigation(LookalikeUrlMatchType match_type, bool ShouldBlockLookalikeUrlNavigation(LookalikeUrlMatchType match_type);
const DomainInfo& navigated_domain);
// Returns true if a domain is visually similar to the hostname of |url|. The // Returns true if a domain is visually similar to the hostname of |url|. The
// matching domain can be a top domain or an engaged site. Similarity // matching domain can be a top domain or an engaged site. Similarity
......
...@@ -114,7 +114,7 @@ void LookalikeUrlTabHelper::ShouldAllowResponse( ...@@ -114,7 +114,7 @@ void LookalikeUrlTabHelper::ShouldAllowResponse(
RecordUMAFromMatchType(match_type); RecordUMAFromMatchType(match_type);
if (ShouldBlockLookalikeUrlNavigation(match_type, navigated_domain)) { if (ShouldBlockLookalikeUrlNavigation(match_type)) {
const std::string suggested_domain = GetETLDPlusOne(matched_domain); const std::string suggested_domain = GetETLDPlusOne(matched_domain);
DCHECK(!suggested_domain.empty()); DCHECK(!suggested_domain.empty());
GURL::Replacements replace_host; GURL::Replacements replace_host;
......
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