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
...@@ -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