| // Copyright (c) 2012 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 <stdint.h> |
| |
| #include <algorithm> |
| |
| #include "base/bind.h" |
| #include "base/macros.h" |
| #include "base/pickle.h" |
| #include "base/run_loop.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/time/time.h" |
| #include "chrome/browser/history/history_service_factory.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/safe_browsing/safe_browsing_service.h" |
| #include "chrome/browser/safe_browsing/ui_manager.h" |
| #include "chrome/test/base/chrome_render_view_host_test_harness.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "components/history/core/browser/history_backend.h" |
| #include "components/history/core/browser/history_service.h" |
| #include "components/safe_browsing/browser/referrer_chain_provider.h" |
| #include "components/safe_browsing/browser/threat_details.h" |
| #include "components/safe_browsing/browser/threat_details_history.h" |
| #include "components/safe_browsing/common/safe_browsing.mojom.h" |
| #include "components/safe_browsing/proto/csd.pb.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/test/navigation_simulator.h" |
| #include "content/public/test/test_renderer_host.h" |
| #include "content/public/test/web_contents_tester.h" |
| #include "net/base/ip_endpoint.h" |
| #include "net/base/net_errors.h" |
| #include "net/http/http_util.h" |
| #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h" |
| #include "services/network/test/test_url_loader_factory.h" |
| #include "testing/gmock/include/gmock/gmock-matchers.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| |
| using content::BrowserThread; |
| using content::WebContents; |
| using testing::_; |
| using testing::DoAll; |
| using testing::Eq; |
| using testing::Return; |
| using testing::SetArgPointee; |
| using testing::UnorderedPointwise; |
| |
| namespace safe_browsing { |
| |
| namespace { |
| |
| // Mixture of HTTP and HTTPS. No special treatment for HTTPS. |
| static const char* kOriginalLandingURL = |
| "http://www.originallandingpage.com/with/path"; |
| static const char* kDOMChildURL = "https://www.domchild.com/with/path"; |
| static const char* kDOMChildUrl2 = "https://www.domchild2.com/path"; |
| static const char* kDOMParentURL = "https://www.domparent.com/with/path"; |
| static const char* kFirstRedirectURL = "http://redirectone.com/with/path"; |
| static const char* kSecondRedirectURL = "https://redirecttwo.com/with/path"; |
| static const char* kReferrerURL = "http://www.referrer.com/with/path"; |
| static const char* kDataURL = "data:text/html;charset=utf-8;base64,PCFET0"; |
| static const char* kBlankURL = "about:blank"; |
| |
| static const char* kThreatURL = "http://www.threat.com/with/path"; |
| static const char* kThreatURLHttps = "https://www.threat.com/with/path"; |
| static const char* kThreatHeaders = |
| "HTTP/1.1 200 OK\n" |
| "Content-Type: image/jpeg\n" |
| "Some-Other-Header: foo\n"; // Persisted for http, stripped for https |
| static const char* kThreatData = "exploit();"; |
| |
| static const char* kLandingURL = "http://www.landingpage.com/with/path"; |
| static const char* kLandingHeaders = |
| "HTTP/1.1 200 OK\n" |
| "Content-Type: text/html\n" |
| "Content-Length: 1024\n" |
| "Set-Cookie: tastycookie\n"; // This header is stripped. |
| static const char* kLandingData = |
| "<iframe src='http://www.threat.com/with/path'>"; |
| |
| using content::BrowserThread; |
| using content::WebContents; |
| |
| // Lets us control synchronization of the done callback for ThreatDetails. |
| // Also exposes the constructor. |
| class ThreatDetailsWrap : public ThreatDetails { |
| public: |
| ThreatDetailsWrap( |
| SafeBrowsingUIManager* ui_manager, |
| WebContents* web_contents, |
| const security_interstitials::UnsafeResource& unsafe_resource, |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, |
| history::HistoryService* history_service, |
| ReferrerChainProvider* referrer_chain_provider) |
| : ThreatDetails(ui_manager, |
| web_contents, |
| unsafe_resource, |
| url_loader_factory, |
| history_service, |
| referrer_chain_provider, |
| /*trim_to_ad_tags=*/false, |
| base::Bind(&ThreatDetailsWrap::ThreatDetailsDone, |
| base::Unretained(this))), |
| run_loop_(nullptr), |
| done_callback_count_(0) {} |
| |
| ThreatDetailsWrap( |
| SafeBrowsingUIManager* ui_manager, |
| WebContents* web_contents, |
| const security_interstitials::UnsafeResource& unsafe_resource, |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, |
| history::HistoryService* history_service, |
| ReferrerChainProvider* referrer_chain_provider, |
| bool trim_to_ad_tags) |
| : ThreatDetails(ui_manager, |
| web_contents, |
| unsafe_resource, |
| url_loader_factory, |
| history_service, |
| referrer_chain_provider, |
| trim_to_ad_tags, |
| base::Bind(&ThreatDetailsWrap::ThreatDetailsDone, |
| base::Unretained(this))), |
| run_loop_(nullptr), |
| done_callback_count_(0) {} |
| |
| ~ThreatDetailsWrap() override {} |
| |
| void ThreatDetailsDone(content::WebContents* web_contents) { |
| ++done_callback_count_; |
| run_loop_->Quit(); |
| run_loop_ = nullptr; |
| } |
| |
| // Used to synchronize ThreatDetailsDone() with WaitForThreatDetailsDone(). |
| // RunLoop::RunUntilIdle() is not sufficient because the MessageLoop task |
| // queue completely drains at some point between the send and the wait. |
| void SetRunLoopToQuit(base::RunLoop* run_loop) { |
| DCHECK(run_loop_ == nullptr); |
| run_loop_ = run_loop; |
| } |
| |
| size_t done_callback_count() { return done_callback_count_; } |
| |
| void StartCollection() { ThreatDetails::StartCollection(); } |
| |
| private: |
| |
| base::RunLoop* run_loop_; |
| size_t done_callback_count_; |
| }; |
| |
| class MockSafeBrowsingUIManager : public SafeBrowsingUIManager { |
| public: |
| // The safe browsing UI manager does not need a service for this test. |
| MockSafeBrowsingUIManager() |
| : SafeBrowsingUIManager(nullptr), report_sent_(false) {} |
| |
| // When the serialized report is sent, this is called. |
| void SendSerializedThreatDetails(const std::string& serialized) override { |
| report_sent_ = true; |
| serialized_ = serialized; |
| } |
| |
| const std::string& GetSerialized() { return serialized_; } |
| bool ReportWasSent() { return report_sent_; } |
| |
| private: |
| ~MockSafeBrowsingUIManager() override {} |
| |
| std::string serialized_; |
| bool report_sent_; |
| DISALLOW_COPY_AND_ASSIGN(MockSafeBrowsingUIManager); |
| }; |
| |
| class MockReferrerChainProvider : public ReferrerChainProvider { |
| public: |
| virtual ~MockReferrerChainProvider() {} |
| MOCK_METHOD3(IdentifyReferrerChainByWebContents, |
| AttributionResult(content::WebContents* web_contents, |
| int user_gesture_count_limit, |
| ReferrerChain* out_referrer_chain)); |
| MOCK_METHOD4(IdentifyReferrerChainByEventURL, |
| AttributionResult(const GURL& event_url, |
| SessionID event_tab_id, |
| int user_gesture_count_limit, |
| ReferrerChain* out_referrer_chain)); |
| }; |
| |
| } // namespace |
| |
| class ThreatDetailsTest : public ChromeRenderViewHostTestHarness { |
| public: |
| typedef SafeBrowsingUIManager::UnsafeResource UnsafeResource; |
| |
| ThreatDetailsTest() |
| : referrer_chain_provider_(new MockReferrerChainProvider()), |
| ui_manager_(new MockSafeBrowsingUIManager()) {} |
| |
| void SetUp() override { |
| ChromeRenderViewHostTestHarness::SetUp(); |
| ASSERT_TRUE(profile()->CreateHistoryService(true /* delete_file */, |
| false /* no_db */)); |
| test_shared_loader_factory_ = |
| base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>( |
| &test_url_loader_factory_); |
| } |
| |
| std::string WaitForThreatDetailsDone(ThreatDetailsWrap* report, |
| bool did_proceed, |
| int num_visit) { |
| report->FinishCollection(did_proceed, num_visit); |
| // Wait for the callback (ThreatDetailsDone). |
| base::RunLoop run_loop; |
| report->SetRunLoopToQuit(&run_loop); |
| run_loop.Run(); |
| // Make sure the done callback was run exactly once. |
| EXPECT_EQ(1u, report->done_callback_count()); |
| return ui_manager_->GetSerialized(); |
| } |
| |
| history::HistoryService* history_service() { |
| return HistoryServiceFactory::GetForProfile( |
| profile(), ServiceAccessType::EXPLICIT_ACCESS); |
| } |
| |
| bool ReportWasSent() { return ui_manager_->ReportWasSent(); } |
| |
| protected: |
| void InitResource(SBThreatType threat_type, |
| ThreatSource threat_source, |
| bool is_subresource, |
| const GURL& url, |
| UnsafeResource* resource) { |
| resource->url = url; |
| resource->is_subresource = is_subresource; |
| resource->threat_type = threat_type; |
| resource->threat_source = threat_source; |
| resource->web_contents_getter = |
| SafeBrowsingUIManager::UnsafeResource::GetWebContentsGetter( |
| web_contents()->GetMainFrame()->GetProcess()->GetID(), |
| web_contents()->GetMainFrame()->GetRoutingID()); |
| } |
| |
| void VerifyResults(const ClientSafeBrowsingReportRequest& report_pb, |
| const ClientSafeBrowsingReportRequest& expected_pb) { |
| EXPECT_EQ(expected_pb.type(), report_pb.type()); |
| EXPECT_EQ(expected_pb.url(), report_pb.url()); |
| EXPECT_EQ(expected_pb.page_url(), report_pb.page_url()); |
| EXPECT_EQ(expected_pb.referrer_url(), report_pb.referrer_url()); |
| EXPECT_EQ(expected_pb.did_proceed(), report_pb.did_proceed()); |
| EXPECT_EQ(expected_pb.has_repeat_visit(), report_pb.has_repeat_visit()); |
| if (expected_pb.has_repeat_visit() && report_pb.has_repeat_visit()) { |
| EXPECT_EQ(expected_pb.repeat_visit(), report_pb.repeat_visit()); |
| } |
| |
| ASSERT_EQ(expected_pb.resources_size(), report_pb.resources_size()); |
| // Put the actual resources in a map, then iterate over the expected |
| // resources, making sure each exists and is equal. |
| std::map<int, const ClientSafeBrowsingReportRequest::Resource*> |
| actual_resource_map; |
| for (const ClientSafeBrowsingReportRequest::Resource& resource : |
| report_pb.resources()) { |
| actual_resource_map[resource.id()] = &resource; |
| } |
| // Make sure no resources disappeared when moving them to a map (IDs should |
| // be unique). |
| ASSERT_EQ(expected_pb.resources_size(), |
| static_cast<int>(actual_resource_map.size())); |
| for (const ClientSafeBrowsingReportRequest::Resource& expected_resource : |
| expected_pb.resources()) { |
| ASSERT_GT(actual_resource_map.count(expected_resource.id()), 0u); |
| VerifyResource(*actual_resource_map[expected_resource.id()], |
| expected_resource); |
| } |
| |
| ASSERT_EQ(expected_pb.dom_size(), report_pb.dom_size()); |
| // Put the actual elements in a map, then iterate over the expected |
| // elements, making sure each exists and is equal. |
| std::map<int, const HTMLElement*> actual_dom_map; |
| for (const HTMLElement& element : report_pb.dom()) { |
| actual_dom_map[element.id()] = &element; |
| } |
| // Make sure no elements disappeared when moving them to a map (IDs should |
| // be unique). |
| ASSERT_EQ(expected_pb.dom_size(), static_cast<int>(actual_dom_map.size())); |
| for (const HTMLElement& expected_element : expected_pb.dom()) { |
| ASSERT_GT(actual_dom_map.count(expected_element.id()), 0u); |
| VerifyElement(*actual_dom_map[expected_element.id()], expected_element); |
| } |
| |
| EXPECT_TRUE(report_pb.client_properties().has_url_api_type()); |
| EXPECT_EQ(expected_pb.client_properties().url_api_type(), |
| report_pb.client_properties().url_api_type()); |
| EXPECT_EQ(expected_pb.complete(), report_pb.complete()); |
| |
| EXPECT_EQ(expected_pb.referrer_chain_size(), |
| report_pb.referrer_chain_size()); |
| // TODO: check each elem url |
| } |
| |
| void VerifyResource( |
| const ClientSafeBrowsingReportRequest::Resource& resource, |
| const ClientSafeBrowsingReportRequest::Resource& expected) { |
| EXPECT_EQ(expected.id(), resource.id()); |
| EXPECT_EQ(expected.url(), resource.url()); |
| EXPECT_EQ(expected.parent_id(), resource.parent_id()); |
| ASSERT_EQ(expected.child_ids_size(), resource.child_ids_size()); |
| for (int i = 0; i < expected.child_ids_size(); i++) { |
| EXPECT_EQ(expected.child_ids(i), resource.child_ids(i)); |
| } |
| |
| // Verify HTTP Responses |
| if (expected.has_response()) { |
| ASSERT_TRUE(resource.has_response()); |
| EXPECT_EQ(expected.response().firstline().code(), |
| resource.response().firstline().code()); |
| |
| ASSERT_EQ(expected.response().headers_size(), |
| resource.response().headers_size()); |
| for (int i = 0; i < expected.response().headers_size(); ++i) { |
| EXPECT_EQ(expected.response().headers(i).name(), |
| resource.response().headers(i).name()); |
| EXPECT_EQ(expected.response().headers(i).value(), |
| resource.response().headers(i).value()); |
| } |
| |
| EXPECT_EQ(expected.response().body(), resource.response().body()); |
| EXPECT_EQ(expected.response().bodylength(), |
| resource.response().bodylength()); |
| EXPECT_EQ(expected.response().bodydigest(), |
| resource.response().bodydigest()); |
| } |
| |
| // Verify IP:port pair |
| EXPECT_EQ(expected.response().remote_ip(), resource.response().remote_ip()); |
| } |
| |
| void VerifyElement(const HTMLElement& element, const HTMLElement& expected) { |
| EXPECT_EQ(expected.id(), element.id()); |
| EXPECT_EQ(expected.tag(), element.tag()); |
| EXPECT_EQ(expected.resource_id(), element.resource_id()); |
| EXPECT_THAT(element.child_ids(), |
| UnorderedPointwise(Eq(), expected.child_ids())); |
| ASSERT_EQ(expected.attribute_size(), element.attribute_size()); |
| std::map<std::string, std::string> actual_attributes_map; |
| for (const HTMLElement::Attribute& attribute : element.attribute()) { |
| actual_attributes_map[attribute.name()] = attribute.value(); |
| } |
| ASSERT_EQ(expected.attribute_size(), |
| static_cast<int>(actual_attributes_map.size())); |
| for (const HTMLElement::Attribute& expected_attribute : |
| expected.attribute()) { |
| ASSERT_GT(actual_attributes_map.count(expected_attribute.name()), 0u); |
| EXPECT_EQ(expected_attribute.value(), |
| actual_attributes_map[expected_attribute.name()]); |
| } |
| } |
| // Adds a page to history. |
| // The redirects is the redirect url chain leading to the url. |
| void AddPageToHistory(const GURL& url, history::RedirectList* redirects) { |
| // The last item of the redirect chain has to be the final url when adding |
| // to history backend. |
| redirects->push_back(url); |
| history_service()->AddPage(url, base::Time::Now(), |
| reinterpret_cast<history::ContextID>(1), 0, |
| GURL(), *redirects, ui::PAGE_TRANSITION_TYPED, |
| history::SOURCE_BROWSED, false); |
| } |
| |
| void WriteCacheEntry(const std::string& url, |
| const std::string& headers, |
| const std::string& content) { |
| network::ResourceResponseHead head; |
| head.headers = base::MakeRefCounted<net::HttpResponseHeaders>( |
| net::HttpUtil::AssembleRawHeaders(headers)); |
| head.remote_endpoint = net::IPEndPoint(net::IPAddress(1, 2, 3, 4), 80); |
| head.mime_type = "text/html"; |
| network::URLLoaderCompletionStatus status; |
| status.decoded_body_length = content.size(); |
| |
| test_url_loader_factory_.AddResponse(GURL(url), head, content, status); |
| } |
| |
| void SimulateFillCache(const std::string& url) { |
| WriteCacheEntry(url, kThreatHeaders, kThreatData); |
| WriteCacheEntry(kLandingURL, kLandingHeaders, kLandingData); |
| } |
| |
| std::unique_ptr<MockReferrerChainProvider> referrer_chain_provider_; |
| scoped_refptr<MockSafeBrowsingUIManager> ui_manager_; |
| network::TestURLLoaderFactory test_url_loader_factory_; |
| scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory_; |
| }; |
| |
| // Tests creating a simple threat report of a malware URL. |
| TEST_F(ThreatDetailsTest, ThreatSubResource) { |
| auto navigation = content::NavigationSimulator::CreateBrowserInitiated( |
| GURL(kLandingURL), web_contents()); |
| navigation->SetReferrer(content::Referrer( |
| GURL(kReferrerURL), network::mojom::ReferrerPolicy::kDefault)); |
| navigation->Commit(); |
| |
| UnsafeResource resource; |
| InitResource(SB_THREAT_TYPE_URL_MALWARE, ThreatSource::CLIENT_SIDE_DETECTION, |
| true /* is_subresource */, GURL(kThreatURL), &resource); |
| |
| auto report = std::make_unique<ThreatDetailsWrap>( |
| ui_manager_.get(), web_contents(), resource, nullptr, history_service(), |
| referrer_chain_provider_.get()); |
| report->StartCollection(); |
| |
| std::string serialized = WaitForThreatDetailsDone( |
| report.get(), true /* did_proceed*/, 1 /* num_visit */); |
| |
| ClientSafeBrowsingReportRequest actual; |
| actual.ParseFromString(serialized); |
| |
| ClientSafeBrowsingReportRequest expected; |
| expected.set_type(ClientSafeBrowsingReportRequest::URL_MALWARE); |
| expected.set_url(kThreatURL); |
| expected.set_page_url(kLandingURL); |
| // Note that the referrer policy is not actually enacted here, since that's |
| // done in Blink. |
| expected.set_referrer_url(kReferrerURL); |
| expected.set_did_proceed(true); |
| expected.set_repeat_visit(true); |
| |
| ClientSafeBrowsingReportRequest::Resource* pb_resource = |
| expected.add_resources(); |
| pb_resource->set_id(0); |
| pb_resource->set_url(kLandingURL); |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(1); |
| pb_resource->set_url(kThreatURL); |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(2); |
| pb_resource->set_url(kReferrerURL); |
| |
| VerifyResults(actual, expected); |
| } |
| |
| // Tests creating a simple threat report of a suspicious site that contains |
| // the referrer chain. |
| TEST_F(ThreatDetailsTest, SuspiciousSiteWithReferrerChain) { |
| auto navigation = content::NavigationSimulator::CreateBrowserInitiated( |
| GURL(kLandingURL), web_contents()); |
| navigation->SetReferrer(content::Referrer( |
| GURL(kReferrerURL), network::mojom::ReferrerPolicy::kDefault)); |
| navigation->Commit(); |
| |
| UnsafeResource resource; |
| InitResource(SB_THREAT_TYPE_SUSPICIOUS_SITE, ThreatSource::LOCAL_PVER4, |
| true /* is_subresource */, GURL(kThreatURL), &resource); |
| |
| ReferrerChain returned_referrer_chain; |
| returned_referrer_chain.Add()->set_url(kReferrerURL); |
| returned_referrer_chain.Add()->set_url(kSecondRedirectURL); |
| EXPECT_CALL(*referrer_chain_provider_, |
| IdentifyReferrerChainByWebContents(web_contents(), _, _)) |
| .WillOnce(DoAll(SetArgPointee<2>(returned_referrer_chain), |
| Return(ReferrerChainProvider::SUCCESS))); |
| |
| auto report = std::make_unique<ThreatDetailsWrap>( |
| ui_manager_.get(), web_contents(), resource, nullptr, history_service(), |
| referrer_chain_provider_.get()); |
| report->StartCollection(); |
| |
| std::string serialized = WaitForThreatDetailsDone( |
| report.get(), true /* did_proceed*/, 1 /* num_visit */); |
| |
| ClientSafeBrowsingReportRequest actual; |
| actual.ParseFromString(serialized); |
| |
| ClientSafeBrowsingReportRequest expected; |
| expected.set_type(ClientSafeBrowsingReportRequest::URL_SUSPICIOUS); |
| expected.mutable_client_properties()->set_url_api_type( |
| ClientSafeBrowsingReportRequest::PVER4_NATIVE); |
| expected.set_url(kThreatURL); |
| expected.set_page_url(kLandingURL); |
| // Note that the referrer policy is not actually enacted here, since that's |
| // done in Blink. |
| expected.set_referrer_url(kReferrerURL); |
| expected.set_did_proceed(true); |
| expected.set_repeat_visit(true); |
| |
| ClientSafeBrowsingReportRequest::Resource* pb_resource = |
| expected.add_resources(); |
| pb_resource->set_id(0); |
| pb_resource->set_url(kLandingURL); |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(1); |
| pb_resource->set_url(kThreatURL); |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(2); |
| pb_resource->set_url(kReferrerURL); |
| |
| // Make sure the referrer chain returned by the provider is copied into the |
| // resulting proto. |
| expected.mutable_referrer_chain()->CopyFrom(returned_referrer_chain); |
| |
| VerifyResults(actual, expected); |
| } |
| // Tests creating a simple threat report of a phishing page where the |
| // subresource has a different original_url. |
| TEST_F(ThreatDetailsTest, ThreatSubResourceWithOriginalUrl) { |
| content::WebContentsTester::For(web_contents()) |
| ->NavigateAndCommit(GURL(kLandingURL)); |
| |
| UnsafeResource resource; |
| InitResource(SB_THREAT_TYPE_URL_PHISHING, ThreatSource::DATA_SAVER, |
| true /* is_subresource */, GURL(kThreatURL), &resource); |
| resource.original_url = GURL(kOriginalLandingURL); |
| |
| auto report = std::make_unique<ThreatDetailsWrap>( |
| ui_manager_.get(), web_contents(), resource, nullptr, history_service(), |
| referrer_chain_provider_.get()); |
| report->StartCollection(); |
| |
| std::string serialized = WaitForThreatDetailsDone( |
| report.get(), false /* did_proceed*/, 1 /* num_visit */); |
| |
| ClientSafeBrowsingReportRequest actual; |
| actual.ParseFromString(serialized); |
| |
| ClientSafeBrowsingReportRequest expected; |
| expected.set_type(ClientSafeBrowsingReportRequest::URL_PHISHING); |
| expected.mutable_client_properties()->set_url_api_type( |
| ClientSafeBrowsingReportRequest::FLYWHEEL); |
| expected.set_url(kThreatURL); |
| expected.set_page_url(kLandingURL); |
| expected.set_referrer_url(""); |
| expected.set_did_proceed(false); |
| expected.set_repeat_visit(true); |
| |
| ClientSafeBrowsingReportRequest::Resource* pb_resource = |
| expected.add_resources(); |
| pb_resource->set_id(0); |
| pb_resource->set_url(kLandingURL); |
| |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(1); |
| pb_resource->set_url(kOriginalLandingURL); |
| |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(2); |
| pb_resource->set_url(kThreatURL); |
| // The Resource for kThreatURL should have the Resource for |
| // kOriginalLandingURL (with id 1) as parent. |
| pb_resource->set_parent_id(1); |
| |
| VerifyResults(actual, expected); |
| } |
| |
| // Tests creating a threat report of a UwS page with data from the renderer. |
| TEST_F(ThreatDetailsTest, ThreatDOMDetails) { |
| content::WebContentsTester::For(web_contents()) |
| ->NavigateAndCommit(GURL(kLandingURL)); |
| |
| UnsafeResource resource; |
| InitResource(SB_THREAT_TYPE_URL_UNWANTED, ThreatSource::LOCAL_PVER3, |
| true /* is_subresource */, GURL(kThreatURL), &resource); |
| |
| auto report = std::make_unique<ThreatDetailsWrap>( |
| ui_manager_.get(), web_contents(), resource, nullptr, history_service(), |
| referrer_chain_provider_.get()); |
| report->StartCollection(); |
| |
| // Send a message from the DOM, with 2 nodes, a parent and a child. |
| std::vector<mojom::ThreatDOMDetailsNodePtr> params; |
| mojom::ThreatDOMDetailsNodePtr child_node = |
| mojom::ThreatDOMDetailsNode::New(); |
| child_node->url = GURL(kDOMChildURL); |
| child_node->tag_name = "iframe"; |
| child_node->parent = GURL(kDOMParentURL); |
| child_node->attributes.push_back( |
| mojom::AttributeNameValue::New("src", kDOMChildURL)); |
| params.push_back(std::move(child_node)); |
| mojom::ThreatDOMDetailsNodePtr parent_node = |
| mojom::ThreatDOMDetailsNode::New(); |
| parent_node->url = GURL(kDOMParentURL); |
| parent_node->children.push_back(GURL(kDOMChildURL)); |
| params.push_back(std::move(parent_node)); |
| report->OnReceivedThreatDOMDetails(nullptr, main_rfh(), std::move(params)); |
| |
| std::string serialized = WaitForThreatDetailsDone( |
| report.get(), false /* did_proceed*/, 0 /* num_visit */); |
| ClientSafeBrowsingReportRequest actual; |
| actual.ParseFromString(serialized); |
| |
| ClientSafeBrowsingReportRequest expected; |
| expected.set_type(ClientSafeBrowsingReportRequest::URL_UNWANTED); |
| expected.mutable_client_properties()->set_url_api_type( |
| ClientSafeBrowsingReportRequest::PVER3_NATIVE); |
| expected.set_url(kThreatURL); |
| expected.set_page_url(kLandingURL); |
| expected.set_referrer_url(""); |
| expected.set_did_proceed(false); |
| expected.set_repeat_visit(false); |
| |
| ClientSafeBrowsingReportRequest::Resource* pb_resource = |
| expected.add_resources(); |
| pb_resource->set_id(0); |
| pb_resource->set_url(kLandingURL); |
| |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(1); |
| pb_resource->set_url(kThreatURL); |
| |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(2); |
| pb_resource->set_url(kDOMChildURL); |
| pb_resource->set_parent_id(3); |
| |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(3); |
| pb_resource->set_url(kDOMParentURL); |
| pb_resource->add_child_ids(2); |
| expected.set_complete(false); // Since the cache was missing. |
| |
| HTMLElement* pb_element = expected.add_dom(); |
| pb_element->set_id(0); |
| pb_element->set_tag("IFRAME"); |
| pb_element->set_resource_id(2); |
| pb_element->add_attribute()->set_name("src"); |
| pb_element->mutable_attribute(0)->set_value(kDOMChildURL); |
| |
| VerifyResults(actual, expected); |
| } |
| |
| // Tests creating a threat report when receiving data from multiple renderers. |
| // We use three layers in this test: |
| // kDOMParentURL |
| // \- <div id=outer> |
| // \- <iframe src=kDOMChildURL foo=bar> |
| // \- <div id=inner bar=baz/> - div and script are at the same level. |
| // \- <script src=kDOMChildURL2> |
| TEST_F(ThreatDetailsTest, ThreatDOMDetails_MultipleFrames) { |
| // Create a child renderer inside the main frame to house the inner iframe. |
| // Perform the navigation first in order to manipulate the frame tree. |
| content::WebContentsTester::For(web_contents()) |
| ->NavigateAndCommit(GURL(kLandingURL)); |
| content::RenderFrameHost* child_rfh = |
| content::RenderFrameHostTester::For(main_rfh())->AppendChild("subframe"); |
| |
| // Define two sets of DOM nodes - one for an outer page containing an iframe, |
| // and then another for the inner page containing the contents of that iframe. |
| std::vector<mojom::ThreatDOMDetailsNodePtr> outer_params; |
| mojom::ThreatDOMDetailsNodePtr outer_child_div = |
| mojom::ThreatDOMDetailsNode::New(); |
| outer_child_div->node_id = 1; |
| outer_child_div->child_node_ids.push_back(2); |
| outer_child_div->tag_name = "div"; |
| outer_child_div->parent = GURL(kDOMParentURL); |
| outer_child_div->attributes.push_back( |
| mojom::AttributeNameValue::New("id", "outer")); |
| outer_params.push_back(std::move(outer_child_div)); |
| |
| mojom::ThreatDOMDetailsNodePtr outer_child_iframe = |
| mojom::ThreatDOMDetailsNode::New(); |
| outer_child_iframe->node_id = 2; |
| outer_child_iframe->parent_node_id = 1; |
| outer_child_iframe->url = GURL(kDOMChildURL); |
| outer_child_iframe->tag_name = "iframe"; |
| outer_child_iframe->parent = GURL(kDOMParentURL); |
| outer_child_iframe->attributes.push_back( |
| mojom::AttributeNameValue::New("src", kDOMChildURL)); |
| outer_child_iframe->attributes.push_back( |
| mojom::AttributeNameValue::New("foo", "bar")); |
| outer_child_iframe->child_frame_routing_id = child_rfh->GetRoutingID(); |
| outer_params.push_back(std::move(outer_child_iframe)); |
| |
| mojom::ThreatDOMDetailsNodePtr outer_summary_node = |
| mojom::ThreatDOMDetailsNode::New(); |
| outer_summary_node->url = GURL(kDOMParentURL); |
| outer_summary_node->children.push_back(GURL(kDOMChildURL)); |
| outer_params.push_back(std::move(outer_summary_node)); |
| |
| // Now define some more nodes for the body of the iframe. |
| std::vector<mojom::ThreatDOMDetailsNodePtr> inner_params; |
| mojom::ThreatDOMDetailsNodePtr inner_child_div = |
| mojom::ThreatDOMDetailsNode::New(); |
| inner_child_div->node_id = 1; |
| inner_child_div->tag_name = "div"; |
| inner_child_div->parent = GURL(kDOMChildURL); |
| inner_child_div->attributes.push_back( |
| mojom::AttributeNameValue::New("id", "inner")); |
| inner_child_div->attributes.push_back( |
| mojom::AttributeNameValue::New("bar", "baz")); |
| inner_params.push_back(std::move(inner_child_div)); |
| |
| mojom::ThreatDOMDetailsNodePtr inner_child_script = |
| mojom::ThreatDOMDetailsNode::New(); |
| inner_child_script->node_id = 2; |
| inner_child_script->url = GURL(kDOMChildUrl2); |
| inner_child_script->tag_name = "script"; |
| inner_child_script->parent = GURL(kDOMChildURL); |
| inner_child_script->attributes.push_back( |
| mojom::AttributeNameValue::New("src", kDOMChildUrl2)); |
| inner_params.push_back(std::move(inner_child_script)); |
| |
| mojom::ThreatDOMDetailsNodePtr inner_summary_node = |
| mojom::ThreatDOMDetailsNode::New(); |
| inner_summary_node->url = GURL(kDOMChildURL); |
| inner_summary_node->children.push_back(GURL(kDOMChildUrl2)); |
| inner_params.push_back(std::move(inner_summary_node)); |
| |
| ClientSafeBrowsingReportRequest expected; |
| expected.set_type(ClientSafeBrowsingReportRequest::URL_UNWANTED); |
| expected.mutable_client_properties()->set_url_api_type( |
| ClientSafeBrowsingReportRequest::PVER4_NATIVE); |
| expected.set_url(kThreatURL); |
| expected.set_page_url(kLandingURL); |
| expected.set_referrer_url(""); |
| expected.set_did_proceed(false); |
| expected.set_repeat_visit(false); |
| |
| ClientSafeBrowsingReportRequest::Resource* pb_resource = |
| expected.add_resources(); |
| pb_resource->set_id(0); |
| pb_resource->set_url(kLandingURL); |
| |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(1); |
| pb_resource->set_url(kThreatURL); |
| |
| ClientSafeBrowsingReportRequest::Resource* res_dom_child = |
| expected.add_resources(); |
| res_dom_child->set_id(2); |
| res_dom_child->set_url(kDOMChildURL); |
| res_dom_child->set_parent_id(3); |
| res_dom_child->add_child_ids(4); |
| |
| ClientSafeBrowsingReportRequest::Resource* res_dom_parent = |
| expected.add_resources(); |
| res_dom_parent->set_id(3); |
| res_dom_parent->set_url(kDOMParentURL); |
| res_dom_parent->add_child_ids(2); |
| |
| ClientSafeBrowsingReportRequest::Resource* res_dom_child2 = |
| expected.add_resources(); |
| res_dom_child2->set_id(4); |
| res_dom_child2->set_url(kDOMChildUrl2); |
| res_dom_child2->set_parent_id(2); |
| |
| expected.set_complete(false); // Since the cache was missing. |
| |
| HTMLElement* elem_dom_outer_div = expected.add_dom(); |
| elem_dom_outer_div->set_id(0); |
| elem_dom_outer_div->set_tag("DIV"); |
| elem_dom_outer_div->add_attribute()->set_name("id"); |
| elem_dom_outer_div->mutable_attribute(0)->set_value("outer"); |
| elem_dom_outer_div->add_child_ids(1); |
| |
| HTMLElement* elem_dom_outer_iframe = expected.add_dom(); |
| elem_dom_outer_iframe->set_id(1); |
| elem_dom_outer_iframe->set_tag("IFRAME"); |
| elem_dom_outer_iframe->set_resource_id(res_dom_child->id()); |
| elem_dom_outer_iframe->add_attribute()->set_name("src"); |
| elem_dom_outer_iframe->mutable_attribute(0)->set_value(kDOMChildURL); |
| elem_dom_outer_iframe->add_attribute()->set_name("foo"); |
| elem_dom_outer_iframe->mutable_attribute(1)->set_value("bar"); |
| elem_dom_outer_iframe->add_child_ids(2); |
| elem_dom_outer_iframe->add_child_ids(3); |
| |
| HTMLElement* elem_dom_inner_div = expected.add_dom(); |
| elem_dom_inner_div->set_id(2); |
| elem_dom_inner_div->set_tag("DIV"); |
| elem_dom_inner_div->add_attribute()->set_name("id"); |
| elem_dom_inner_div->mutable_attribute(0)->set_value("inner"); |
| elem_dom_inner_div->add_attribute()->set_name("bar"); |
| elem_dom_inner_div->mutable_attribute(1)->set_value("baz"); |
| |
| HTMLElement* elem_dom_inner_script = expected.add_dom(); |
| elem_dom_inner_script->set_id(3); |
| elem_dom_inner_script->set_tag("SCRIPT"); |
| elem_dom_inner_script->set_resource_id(res_dom_child2->id()); |
| elem_dom_inner_script->add_attribute()->set_name("src"); |
| elem_dom_inner_script->mutable_attribute(0)->set_value(kDOMChildUrl2); |
| |
| UnsafeResource resource; |
| InitResource(SB_THREAT_TYPE_URL_UNWANTED, ThreatSource::LOCAL_PVER4, |
| true /* is_subresource */, GURL(kThreatURL), &resource); |
| |
| // Send both sets of nodes, from different render frames. |
| { |
| auto report = std::make_unique<ThreatDetailsWrap>( |
| ui_manager_.get(), web_contents(), resource, nullptr, history_service(), |
| referrer_chain_provider_.get()); |
| report->StartCollection(); |
| |
| std::vector<mojom::ThreatDOMDetailsNodePtr> outer_params_copy; |
| for (auto& node : outer_params) { |
| outer_params_copy.push_back(node.Clone()); |
| } |
| std::vector<mojom::ThreatDOMDetailsNodePtr> inner_params_copy; |
| for (auto& node : inner_params) { |
| inner_params_copy.push_back(node.Clone()); |
| } |
| |
| // Send both sets of nodes from different render frames. |
| report->OnReceivedThreatDOMDetails(nullptr, main_rfh(), |
| std::move(outer_params_copy)); |
| report->OnReceivedThreatDOMDetails(nullptr, child_rfh, |
| std::move(inner_params_copy)); |
| |
| std::string serialized = WaitForThreatDetailsDone( |
| report.get(), false /* did_proceed*/, 0 /* num_visit */); |
| ClientSafeBrowsingReportRequest actual; |
| actual.ParseFromString(serialized); |
| VerifyResults(actual, expected); |
| } |
| |
| // Try again but with the messages coming in a different order. The IDs change |
| // slightly, but everything else remains the same. |
| { |
| // Adjust the expected IDs: the inner params come first, so InnerScript |
| // and InnerDiv appear before DomParent |
| res_dom_child2->set_id(2); |
| res_dom_child2->set_parent_id(3); |
| res_dom_child->set_id(3); |
| res_dom_child->set_parent_id(4); |
| res_dom_child->clear_child_ids(); |
| res_dom_child->add_child_ids(2); |
| res_dom_parent->set_id(4); |
| res_dom_parent->clear_child_ids(); |
| res_dom_parent->add_child_ids(3); |
| |
| // Also adjust the elements - they change order since InnerDiv and |
| // InnerScript come in first. |
| elem_dom_inner_div->set_id(0); |
| elem_dom_inner_script->set_id(1); |
| elem_dom_inner_script->set_resource_id(res_dom_child2->id()); |
| |
| elem_dom_outer_div->set_id(2); |
| elem_dom_outer_div->clear_child_ids(); |
| elem_dom_outer_div->add_child_ids(3); |
| elem_dom_outer_iframe->set_id(3); |
| elem_dom_outer_iframe->set_resource_id(res_dom_child->id()); |
| elem_dom_outer_iframe->clear_child_ids(); |
| elem_dom_outer_iframe->add_child_ids(0); |
| elem_dom_outer_iframe->add_child_ids(1); |
| |
| auto report = std::make_unique<ThreatDetailsWrap>( |
| ui_manager_.get(), web_contents(), resource, nullptr, history_service(), |
| referrer_chain_provider_.get()); |
| report->StartCollection(); |
| |
| // Send both sets of nodes from different render frames. |
| report->OnReceivedThreatDOMDetails(nullptr, child_rfh, |
| std::move(inner_params)); |
| report->OnReceivedThreatDOMDetails(nullptr, main_rfh(), |
| std::move(outer_params)); |
| |
| std::string serialized = WaitForThreatDetailsDone( |
| report.get(), false /* did_proceed*/, 0 /* num_visit */); |
| ClientSafeBrowsingReportRequest actual; |
| actual.ParseFromString(serialized); |
| VerifyResults(actual, expected); |
| } |
| } |
| |
| // Tests an ambiguous DOM, meaning that an inner render frame can not be mapped |
| // to an iframe element in the parent frame, which is a failure to lookup the |
| // frames in the frame tree and should not happen. |
| // We use three layers in this test: |
| // kDOMParentURL |
| // \- <frame src=kDataURL> |
| // \- <script src=kDOMChildURL2> |
| TEST_F(ThreatDetailsTest, ThreatDOMDetails_AmbiguousDOM) { |
| const char kAmbiguousDomMetric[] = "SafeBrowsing.ThreatReport.DomIsAmbiguous"; |
| |
| // Create a child renderer inside the main frame to house the inner iframe. |
| // Perform the navigation first in order to manipulate the frame tree. |
| content::WebContentsTester::For(web_contents()) |
| ->NavigateAndCommit(GURL(kLandingURL)); |
| content::RenderFrameHost* child_rfh = |
| content::RenderFrameHostTester::For(main_rfh())->AppendChild("subframe"); |
| // Define two sets of DOM nodes - one for an outer page containing a frame, |
| // and then another for the inner page containing the contents of that frame. |
| std::vector<mojom::ThreatDOMDetailsNodePtr> outer_params; |
| mojom::ThreatDOMDetailsNodePtr outer_child_node = |
| mojom::ThreatDOMDetailsNode::New(); |
| outer_child_node->url = GURL(kDataURL); |
| outer_child_node->tag_name = "frame"; |
| outer_child_node->parent = GURL(kDOMParentURL); |
| outer_child_node->attributes.push_back( |
| mojom::AttributeNameValue::New("src", kDataURL)); |
| outer_params.push_back(std::move(outer_child_node)); |
| mojom::ThreatDOMDetailsNodePtr outer_summary_node = |
| mojom::ThreatDOMDetailsNode::New(); |
| outer_summary_node->url = GURL(kDOMParentURL); |
| outer_summary_node->children.push_back(GURL(kDataURL)); |
| // Set |child_frame_routing_id| for this node to something non-sensical so |
| // that the child frame lookup fails. |
| outer_summary_node->child_frame_routing_id = -100; |
| outer_params.push_back(std::move(outer_summary_node)); |
| |
| // Now define some more nodes for the body of the frame. The URL of this |
| // inner frame is "about:blank". |
| std::vector<mojom::ThreatDOMDetailsNodePtr> inner_params; |
| mojom::ThreatDOMDetailsNodePtr inner_child_node = |
| mojom::ThreatDOMDetailsNode::New(); |
| inner_child_node->url = GURL(kDOMChildUrl2); |
| inner_child_node->tag_name = "script"; |
| inner_child_node->parent = GURL(kBlankURL); |
| inner_child_node->attributes.push_back( |
| mojom::AttributeNameValue::New("src", kDOMChildUrl2)); |
| inner_params.push_back(std::move(inner_child_node)); |
| mojom::ThreatDOMDetailsNodePtr inner_summary_node = |
| mojom::ThreatDOMDetailsNode::New(); |
| inner_summary_node->url = GURL(kBlankURL); |
| inner_summary_node->children.push_back(GURL(kDOMChildUrl2)); |
| inner_params.push_back(std::move(inner_summary_node)); |
| |
| ClientSafeBrowsingReportRequest expected; |
| expected.set_type(ClientSafeBrowsingReportRequest::URL_UNWANTED); |
| expected.set_url(kThreatURL); |
| expected.set_page_url(kLandingURL); |
| expected.set_referrer_url(""); |
| expected.set_did_proceed(false); |
| expected.set_repeat_visit(false); |
| |
| ClientSafeBrowsingReportRequest::Resource* pb_resource = |
| expected.add_resources(); |
| pb_resource->set_id(0); |
| pb_resource->set_url(kLandingURL); |
| |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(1); |
| pb_resource->set_url(kThreatURL); |
| |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(2); |
| pb_resource->set_url(kDOMParentURL); |
| pb_resource->add_child_ids(3); |
| |
| // TODO(lpz): The data URL is added, despite being unreportable, because it |
| // is a child of the top-level page. Consider if this should happen. |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(3); |
| pb_resource->set_url(kDataURL); |
| |
| // This child can't be mapped to its containing iframe so its parent is unset. |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(4); |
| pb_resource->set_url(kDOMChildUrl2); |
| |
| expected.set_complete(false); // Since the cache was missing. |
| |
| // This Element represents the Frame with the data URL. It has no resource or |
| // children since it couldn't be mapped to anything. It does still contain the |
| // src attribute with the data URL set. |
| HTMLElement* pb_element = expected.add_dom(); |
| pb_element->set_id(0); |
| pb_element->set_tag("FRAME"); |
| pb_element->add_attribute()->set_name("src"); |
| pb_element->mutable_attribute(0)->set_value(kDataURL); |
| |
| pb_element = expected.add_dom(); |
| pb_element->set_id(1); |
| pb_element->set_tag("SCRIPT"); |
| pb_element->set_resource_id(4); |
| pb_element->add_attribute()->set_name("src"); |
| pb_element->mutable_attribute(0)->set_value(kDOMChildUrl2); |
| |
| UnsafeResource resource; |
| InitResource(SB_THREAT_TYPE_URL_UNWANTED, |
| ThreatSource::PASSWORD_PROTECTION_SERVICE, |
| true /* is_subresource */, GURL(kThreatURL), &resource); |
| auto report = std::make_unique<ThreatDetailsWrap>( |
| ui_manager_.get(), web_contents(), resource, nullptr, history_service(), |
| referrer_chain_provider_.get()); |
| report->StartCollection(); |
| |
| base::HistogramTester histograms; |
| |
| // Send both sets of nodes from different render frames. |
| report->OnReceivedThreatDOMDetails(nullptr, main_rfh(), |
| std::move(outer_params)); |
| report->OnReceivedThreatDOMDetails(nullptr, child_rfh, |
| std::move(inner_params)); |
| std::string serialized = WaitForThreatDetailsDone( |
| report.get(), false /* did_proceed*/, 0 /* num_visit */); |
| ClientSafeBrowsingReportRequest actual; |
| actual.ParseFromString(serialized); |
| VerifyResults(actual, expected); |
| |
| // This DOM should be ambiguous, expect the UMA metric to be incremented. |
| histograms.ExpectTotalCount(kAmbiguousDomMetric, 1); |
| } |
| |
| // Tests creating a threat report when receiving data from multiple renderers |
| // that gets trimmed to just the ad and its contents. |
| // This test uses the following structure. |
| // kDOMParentURL |
| // \- <div id=outer> # Trimmed |
| // \- <script id=shared-resource src=kFirstRedirectURL> # Trimmed |
| // \- <script id=outer-sibling src=kReferrerURL> # Reported (parent of ad ID) |
| // \- <script id=sibling src=kFirstRedirectURL> # Reported (sibling of ad ID) |
| // \- <div data-google-query-id=ad-tag> # Reported (ad ID) |
| // \- <iframe src=kDOMChildURL foo=bar> # Reported (child of ad ID) |
| // \- <div id=inner bar=baz/> # Reported (child of ad ID) |
| // \- <script src=kDOMChildURL2> # Reported (child of ad ID) |
| // |
| // *Note: the best way to match the inputs and expectations in the body of the |
| // test with the structure above, is to use URLs for resources, and the ID |
| // attributes for DOM elements. |
| TEST_F(ThreatDetailsTest, ThreatDOMDetails_TrimToAdTags) { |
| // Create a child renderer inside the main frame to house the inner iframe. |
| // Perform the navigation first in order to manipulate the frame tree. |
| content::WebContentsTester::For(web_contents()) |
| ->NavigateAndCommit(GURL(kLandingURL)); |
| content::RenderFrameHost* child_rfh = |
| content::RenderFrameHostTester::For(main_rfh())->AppendChild("subframe"); |
| |
| // Define two sets of DOM nodes - one for an outer page containing an iframe, |
| // and then another for the inner page containing the contents of that iframe. |
| std::vector<mojom::ThreatDOMDetailsNodePtr> outer_params; |
| mojom::ThreatDOMDetailsNodePtr outer_div = mojom::ThreatDOMDetailsNode::New(); |
| outer_div->node_id = 1; |
| outer_div->tag_name = "div"; |
| outer_div->parent = GURL(kDOMParentURL); |
| outer_div->attributes.push_back( |
| mojom::AttributeNameValue::New("id", "outer")); |
| outer_params.push_back(std::move(outer_div)); |
| |
| mojom::ThreatDOMDetailsNodePtr shared_resource_script = |
| mojom::ThreatDOMDetailsNode::New(); |
| shared_resource_script->node_id = 2; |
| shared_resource_script->tag_name = "script"; |
| shared_resource_script->url = GURL(kFirstRedirectURL); |
| shared_resource_script->parent = GURL(kDOMParentURL); |
| shared_resource_script->attributes.push_back( |
| mojom::AttributeNameValue::New("id", "shared-resource")); |
| outer_params.push_back(std::move(shared_resource_script)); |
| |
| mojom::ThreatDOMDetailsNodePtr outer_sibling_script = |
| mojom::ThreatDOMDetailsNode::New(); |
| outer_sibling_script->node_id = 3; |
| outer_sibling_script->url = GURL(kReferrerURL); |
| outer_sibling_script->child_node_ids.push_back(4); |
| outer_sibling_script->child_node_ids.push_back(5); |
| outer_sibling_script->tag_name = "script"; |
| outer_sibling_script->parent = GURL(kDOMParentURL); |
| outer_sibling_script->attributes.push_back( |
| mojom::AttributeNameValue::New("src", kReferrerURL)); |
| outer_sibling_script->attributes.push_back( |
| mojom::AttributeNameValue::New("id", "outer-sibling")); |
| outer_params.push_back(std::move(outer_sibling_script)); |
| |
| mojom::ThreatDOMDetailsNodePtr sibling_script = |
| mojom::ThreatDOMDetailsNode::New(); |
| sibling_script->node_id = 4; |
| sibling_script->url = GURL(kFirstRedirectURL); |
| sibling_script->tag_name = "script"; |
| sibling_script->parent = GURL(kDOMParentURL); |
| sibling_script->parent_node_id = 3; |
| sibling_script->attributes.push_back( |
| mojom::AttributeNameValue::New("src", kFirstRedirectURL)); |
| sibling_script->attributes.push_back( |
| mojom::AttributeNameValue::New("id", "sibling")); |
| outer_params.push_back(std::move(sibling_script)); |
| |
| mojom::ThreatDOMDetailsNodePtr outer_ad_tag_div = |
| mojom::ThreatDOMDetailsNode::New(); |
| outer_ad_tag_div->node_id = 5; |
| outer_ad_tag_div->parent_node_id = 3; |
| outer_ad_tag_div->child_node_ids.push_back(6); |
| outer_ad_tag_div->tag_name = "div"; |
| outer_ad_tag_div->parent = GURL(kDOMParentURL); |
| outer_ad_tag_div->attributes.push_back( |
| mojom::AttributeNameValue::New("data-google-query-id", "ad-tag")); |
| outer_params.push_back(std::move(outer_ad_tag_div)); |
| |
| mojom::ThreatDOMDetailsNodePtr outer_child_iframe = |
| mojom::ThreatDOMDetailsNode::New(); |
| outer_child_iframe->node_id = 6; |
| outer_child_iframe->parent_node_id = 5; |
| outer_child_iframe->url = GURL(kDOMChildURL); |
| outer_child_iframe->tag_name = "iframe"; |
| outer_child_iframe->parent = GURL(kDOMParentURL); |
| outer_child_iframe->attributes.push_back( |
| mojom::AttributeNameValue::New("src", kDOMChildURL)); |
| outer_child_iframe->attributes.push_back( |
| mojom::AttributeNameValue::New("foo", "bar")); |
| outer_child_iframe->child_frame_routing_id = child_rfh->GetRoutingID(); |
| outer_params.push_back(std::move(outer_child_iframe)); |
| |
| mojom::ThreatDOMDetailsNodePtr outer_summary_node = |
| mojom::ThreatDOMDetailsNode::New(); |
| outer_summary_node->url = GURL(kDOMParentURL); |
| outer_summary_node->children.push_back(GURL(kDOMChildURL)); |
| outer_summary_node->children.push_back(GURL(kReferrerURL)); |
| outer_summary_node->children.push_back(GURL(kFirstRedirectURL)); |
| outer_params.push_back(std::move(outer_summary_node)); |
| |
| // Now define some more nodes for the body of the iframe. |
| std::vector<mojom::ThreatDOMDetailsNodePtr> inner_params; |
| mojom::ThreatDOMDetailsNodePtr inner_child_div = |
| mojom::ThreatDOMDetailsNode::New(); |
| inner_child_div->node_id = 1; |
| inner_child_div->tag_name = "div"; |
| inner_child_div->parent = GURL(kDOMChildURL); |
| inner_child_div->attributes.push_back( |
| mojom::AttributeNameValue::New("id", "inner")); |
| inner_child_div->attributes.push_back( |
| mojom::AttributeNameValue::New("bar", "baz")); |
| inner_params.push_back(std::move(inner_child_div)); |
| |
| mojom::ThreatDOMDetailsNodePtr inner_child_script = |
| mojom::ThreatDOMDetailsNode::New(); |
| inner_child_script->node_id = 2; |
| inner_child_script->url = GURL(kDOMChildUrl2); |
| inner_child_script->tag_name = "script"; |
| inner_child_script->parent = GURL(kDOMChildURL); |
| inner_child_script->attributes.push_back( |
| mojom::AttributeNameValue::New("src", kDOMChildUrl2)); |
| inner_params.push_back(std::move(inner_child_script)); |
| |
| mojom::ThreatDOMDetailsNodePtr inner_summary_node = |
| mojom::ThreatDOMDetailsNode::New(); |
| inner_summary_node->url = GURL(kDOMChildURL); |
| inner_summary_node->children.push_back(GURL(kDOMChildUrl2)); |
| inner_params.push_back(std::move(inner_summary_node)); |
| |
| ClientSafeBrowsingReportRequest expected; |
| expected.set_type(ClientSafeBrowsingReportRequest::URL_UNWANTED); |
| expected.mutable_client_properties()->set_url_api_type( |
| ClientSafeBrowsingReportRequest::PVER4_NATIVE); |
| expected.set_url(kThreatURL); |
| expected.set_page_url(kLandingURL); |
| expected.set_referrer_url(""); |
| expected.set_did_proceed(false); |
| expected.set_repeat_visit(false); |
| expected.set_complete(false); // Since the cache was missing. |
| |
| ClientSafeBrowsingReportRequest::Resource* pb_resource = |
| expected.add_resources(); |
| pb_resource->set_id(0); |
| pb_resource->set_url(kLandingURL); |
| |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(1); |
| pb_resource->set_url(kThreatURL); |
| |
| ClientSafeBrowsingReportRequest::Resource* res_dom_child2 = |
| expected.add_resources(); |
| res_dom_child2->set_id(2); |
| res_dom_child2->set_url(kDOMChildUrl2); |
| res_dom_child2->set_parent_id(3); |
| res_dom_child2->set_tag_name("script"); |
| |
| ClientSafeBrowsingReportRequest::Resource* res_dom_child = |
| expected.add_resources(); |
| res_dom_child->set_id(3); |
| res_dom_child->set_url(kDOMChildURL); |
| res_dom_child->set_parent_id(5); |
| res_dom_child->add_child_ids(2); |
| res_dom_child->set_tag_name("iframe"); |
| |
| ClientSafeBrowsingReportRequest::Resource* res_ad_parent = |
| expected.add_resources(); |
| res_ad_parent->set_id(6); |
| res_ad_parent->set_url(kReferrerURL); |
| res_ad_parent->set_parent_id(5); |
| res_ad_parent->set_tag_name("script"); |
| |
| ClientSafeBrowsingReportRequest::Resource* res_sibling = |
| expected.add_resources(); |
| res_sibling->set_id(4); |
| res_sibling->set_url(kFirstRedirectURL); |
| res_sibling->set_parent_id(5); |
| res_sibling->set_tag_name("script"); |
| |
| ClientSafeBrowsingReportRequest::Resource* res_dom_parent = |
| expected.add_resources(); |
| res_dom_parent->set_id(5); |
| res_dom_parent->set_url(kDOMParentURL); |
| res_dom_parent->add_child_ids(3); |
| res_dom_parent->add_child_ids(6); |
| res_dom_parent->add_child_ids(4); |
| |
| HTMLElement* elem_dom_parent_script = expected.add_dom(); |
| elem_dom_parent_script->set_id(4); |
| elem_dom_parent_script->set_tag("SCRIPT"); |
| elem_dom_parent_script->set_resource_id(res_ad_parent->id()); |
| elem_dom_parent_script->add_attribute()->set_name("src"); |
| elem_dom_parent_script->mutable_attribute(0)->set_value(kReferrerURL); |
| elem_dom_parent_script->add_attribute()->set_name("id"); |
| elem_dom_parent_script->mutable_attribute(1)->set_value("outer-sibling"); |
| elem_dom_parent_script->add_child_ids(5); |
| elem_dom_parent_script->add_child_ids(6); |
| |
| HTMLElement* elem_dom_sibling_script = expected.add_dom(); |
| elem_dom_sibling_script->set_id(5); |
| elem_dom_sibling_script->set_tag("SCRIPT"); |
| elem_dom_sibling_script->set_resource_id(res_sibling->id()); |
| elem_dom_sibling_script->add_attribute()->set_name("src"); |
| elem_dom_sibling_script->mutable_attribute(0)->set_value(kFirstRedirectURL); |
| elem_dom_sibling_script->add_attribute()->set_name("id"); |
| elem_dom_sibling_script->mutable_attribute(1)->set_value("sibling"); |
| |
| HTMLElement* elem_dom_ad_tag_div = expected.add_dom(); |
| elem_dom_ad_tag_div->set_id(6); |
| elem_dom_ad_tag_div->set_tag("DIV"); |
| elem_dom_ad_tag_div->add_attribute()->set_name("data-google-query-id"); |
| elem_dom_ad_tag_div->mutable_attribute(0)->set_value("ad-tag"); |
| elem_dom_ad_tag_div->add_child_ids(7); |
| |
| HTMLElement* elem_dom_outer_iframe = expected.add_dom(); |
| elem_dom_outer_iframe->set_id(7); |
| elem_dom_outer_iframe->set_tag("IFRAME"); |
| elem_dom_outer_iframe->set_resource_id(res_dom_child->id()); |
| elem_dom_outer_iframe->add_attribute()->set_name("src"); |
| elem_dom_outer_iframe->mutable_attribute(0)->set_value(kDOMChildURL); |
| elem_dom_outer_iframe->add_attribute()->set_name("foo"); |
| elem_dom_outer_iframe->mutable_attribute(1)->set_value("bar"); |
| elem_dom_outer_iframe->add_child_ids(0); |
| elem_dom_outer_iframe->add_child_ids(1); |
| |
| HTMLElement* elem_dom_inner_div = expected.add_dom(); |
| elem_dom_inner_div->set_id(0); |
| elem_dom_inner_div->set_tag("DIV"); |
| elem_dom_inner_div->add_attribute()->set_name("id"); |
| elem_dom_inner_div->mutable_attribute(0)->set_value("inner"); |
| elem_dom_inner_div->add_attribute()->set_name("bar"); |
| elem_dom_inner_div->mutable_attribute(1)->set_value("baz"); |
| |
| HTMLElement* elem_dom_inner_script = expected.add_dom(); |
| elem_dom_inner_script->set_id(1); |
| elem_dom_inner_script->set_tag("SCRIPT"); |
| elem_dom_inner_script->set_resource_id(res_dom_child2->id()); |
| elem_dom_inner_script->add_attribute()->set_name("src"); |
| elem_dom_inner_script->mutable_attribute(0)->set_value(kDOMChildUrl2); |
| |
| UnsafeResource resource; |
| InitResource(SB_THREAT_TYPE_URL_UNWANTED, ThreatSource::LOCAL_PVER4, |
| true /* is_subresource */, GURL(kThreatURL), &resource); |
| |
| // Send both sets of nodes, from different render frames. |
| auto trimmed_report = std::make_unique<ThreatDetailsWrap>( |
| ui_manager_.get(), web_contents(), resource, nullptr, history_service(), |
| referrer_chain_provider_.get(), |
| /*trim_to_ad_tags=*/true); |
| trimmed_report->StartCollection(); |
| |
| // Send both sets of nodes from different render frames. |
| trimmed_report->OnReceivedThreatDOMDetails(nullptr, child_rfh, |
| std::move(inner_params)); |
| trimmed_report->OnReceivedThreatDOMDetails(nullptr, main_rfh(), |
| std::move(outer_params)); |
| |
| std::string serialized = WaitForThreatDetailsDone( |
| trimmed_report.get(), false /* did_proceed*/, 0 /* num_visit */); |
| ClientSafeBrowsingReportRequest actual; |
| actual.ParseFromString(serialized); |
| VerifyResults(actual, expected); |
| } |
| |
| // Tests that an empty report does not get sent. Empty reports can result from |
| // trying to trim a report to ad tags when no ad tags are present. |
| // This test uses the following structure. |
| // kDOMParentURL |
| // \- <frame src=kDataURL> |
| // \- <script src=kDOMChildURL2> |
| TEST_F(ThreatDetailsTest, ThreatDOMDetails_EmptyReportNotSent) { |
| // Create a child renderer inside the main frame to house the inner iframe. |
| // Perform the navigation first in order to manipulate the frame tree. |
| content::WebContentsTester::For(web_contents()) |
| ->NavigateAndCommit(GURL(kLandingURL)); |
| content::RenderFrameHost* child_rfh = |
| content::RenderFrameHostTester::For(main_rfh())->AppendChild("subframe"); |
| // Define two sets of DOM nodes - one for an outer page containing a frame, |
| // and then another for the inner page containing the contents of that frame. |
| std::vector<mojom::ThreatDOMDetailsNodePtr> outer_params; |
| mojom::ThreatDOMDetailsNodePtr outer_child_node = |
| mojom::ThreatDOMDetailsNode::New(); |
| outer_child_node->url = GURL(kDataURL); |
| outer_child_node->tag_name = "frame"; |
| outer_child_node->parent = GURL(kDOMParentURL); |
| outer_child_node->attributes.push_back( |
| mojom::AttributeNameValue::New("src", kDataURL)); |
| outer_params.push_back(std::move(outer_child_node)); |
| mojom::ThreatDOMDetailsNodePtr outer_summary_node = |
| mojom::ThreatDOMDetailsNode::New(); |
| outer_summary_node->url = GURL(kDOMParentURL); |
| outer_summary_node->children.push_back(GURL(kDataURL)); |
| // Set |child_frame_routing_id| for this node to something non-sensical so |
| // that the child frame lookup fails. |
| outer_summary_node->child_frame_routing_id = -100; |
| outer_params.push_back(std::move(outer_summary_node)); |
| |
| // Now define some more nodes for the body of the frame. The URL of this |
| // inner frame is "about:blank". |
| std::vector<mojom::ThreatDOMDetailsNodePtr> inner_params; |
| mojom::ThreatDOMDetailsNodePtr inner_child_node = |
| mojom::ThreatDOMDetailsNode::New(); |
| inner_child_node->url = GURL(kDOMChildUrl2); |
| inner_child_node->tag_name = "script"; |
| inner_child_node->parent = GURL(kBlankURL); |
| inner_child_node->attributes.push_back( |
| mojom::AttributeNameValue::New("src", kDOMChildUrl2)); |
| inner_params.push_back(std::move(inner_child_node)); |
| mojom::ThreatDOMDetailsNodePtr inner_summary_node = |
| mojom::ThreatDOMDetailsNode::New(); |
| inner_summary_node->url = GURL(kBlankURL); |
| inner_summary_node->children.push_back(GURL(kDOMChildUrl2)); |
| inner_params.push_back(std::move(inner_summary_node)); |
| |
| UnsafeResource resource; |
| InitResource(SB_THREAT_TYPE_URL_UNWANTED, ThreatSource::LOCAL_PVER4, |
| true /* is_subresource */, GURL(kThreatURL), &resource); |
| |
| // Send both sets of nodes, from different render frames. |
| auto trimmed_report = std::make_unique<ThreatDetailsWrap>( |
| ui_manager_.get(), web_contents(), resource, nullptr, history_service(), |
| referrer_chain_provider_.get(), |
| /*trim_to_ad_tags=*/true); |
| trimmed_report->StartCollection(); |
| |
| // Send both sets of nodes from different render frames. |
| trimmed_report->OnReceivedThreatDOMDetails(nullptr, child_rfh, |
| std::move(inner_params)); |
| trimmed_report->OnReceivedThreatDOMDetails(nullptr, main_rfh(), |
| std::move(outer_params)); |
| |
| std::string serialized = WaitForThreatDetailsDone( |
| trimmed_report.get(), false /* did_proceed*/, 0 /* num_visit */); |
| EXPECT_FALSE(ReportWasSent()); |
| } |
| |
| // Tests creating a threat report of a malware page where there are redirect |
| // urls to an unsafe resource url. |
| TEST_F(ThreatDetailsTest, ThreatWithRedirectUrl) { |
| content::WebContentsTester::For(web_contents()) |
| ->NavigateAndCommit(GURL(kLandingURL)); |
| |
| UnsafeResource resource; |
| InitResource(SB_THREAT_TYPE_URL_MALWARE, ThreatSource::REMOTE, |
| true /* is_subresource */, GURL(kThreatURL), &resource); |
| resource.original_url = GURL(kOriginalLandingURL); |
| |
| // add some redirect urls |
| resource.redirect_urls.push_back(GURL(kFirstRedirectURL)); |
| resource.redirect_urls.push_back(GURL(kSecondRedirectURL)); |
| resource.redirect_urls.push_back(GURL(kThreatURL)); |
| |
| auto report = std::make_unique<ThreatDetailsWrap>( |
| ui_manager_.get(), web_contents(), resource, nullptr, history_service(), |
| referrer_chain_provider_.get()); |
| report->StartCollection(); |
| |
| std::string serialized = WaitForThreatDetailsDone( |
| report.get(), true /* did_proceed*/, 0 /* num_visit */); |
| ClientSafeBrowsingReportRequest actual; |
| actual.ParseFromString(serialized); |
| |
| ClientSafeBrowsingReportRequest expected; |
| expected.set_type(ClientSafeBrowsingReportRequest::URL_MALWARE); |
| expected.mutable_client_properties()->set_url_api_type( |
| ClientSafeBrowsingReportRequest::ANDROID_SAFETYNET); |
| expected.set_url(kThreatURL); |
| expected.set_page_url(kLandingURL); |
| expected.set_referrer_url(""); |
| expected.set_did_proceed(true); |
| expected.set_repeat_visit(false); |
| |
| ClientSafeBrowsingReportRequest::Resource* pb_resource = |
| expected.add_resources(); |
| pb_resource->set_id(0); |
| pb_resource->set_url(kLandingURL); |
| |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(1); |
| pb_resource->set_url(kOriginalLandingURL); |
| |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(2); |
| pb_resource->set_url(kThreatURL); |
| pb_resource->set_parent_id(4); |
| |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(3); |
| pb_resource->set_url(kFirstRedirectURL); |
| pb_resource->set_parent_id(1); |
| |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(4); |
| pb_resource->set_url(kSecondRedirectURL); |
| pb_resource->set_parent_id(3); |
| |
| VerifyResults(actual, expected); |
| } |
| |
| // Test collecting threat details for a blocked main frame load. |
| TEST_F(ThreatDetailsTest, ThreatOnMainPageLoadBlocked) { |
| const char* kUnrelatedReferrerURL = |
| "http://www.unrelatedreferrer.com/some/path"; |
| const char* kUnrelatedURL = "http://www.unrelated.com/some/path"; |
| |
| // Load and commit an unrelated URL. The ThreatDetails should not use this |
| // navigation entry. |
| auto navigation = content::NavigationSimulator::CreateBrowserInitiated( |
| GURL(kUnrelatedURL), web_contents()); |
| navigation->SetReferrer(content::Referrer( |
| GURL(kUnrelatedReferrerURL), network::mojom::ReferrerPolicy::kDefault)); |
| navigation->Commit(); |
| |
| // Start a pending load with a referrer. |
| controller().LoadURL( |
| GURL(kLandingURL), |
| content::Referrer(GURL(kReferrerURL), |
| network::mojom::ReferrerPolicy::kDefault), |
| ui::PAGE_TRANSITION_TYPED, std::string()); |
| |
| // Create UnsafeResource for the pending main page load. |
| UnsafeResource resource; |
| InitResource(SB_THREAT_TYPE_URL_MALWARE, ThreatSource::UNKNOWN, |
| false /* is_subresource */, GURL(kLandingURL), &resource); |
| |
| // Start ThreatDetails collection. |
| auto report = std::make_unique<ThreatDetailsWrap>( |
| ui_manager_.get(), web_contents(), resource, nullptr, history_service(), |
| referrer_chain_provider_.get()); |
| report->StartCollection(); |
| |
| // Simulate clicking don't proceed. |
| controller().DiscardNonCommittedEntries(); |
| |
| // Finish ThreatDetails collection. |
| std::string serialized = WaitForThreatDetailsDone( |
| report.get(), false /* did_proceed*/, 1 /* num_visit */); |
| |
| ClientSafeBrowsingReportRequest actual; |
| actual.ParseFromString(serialized); |
| |
| ClientSafeBrowsingReportRequest expected; |
| expected.set_type(ClientSafeBrowsingReportRequest::URL_MALWARE); |
| expected.set_url(kLandingURL); |
| expected.set_page_url(kLandingURL); |
| // Note that the referrer policy is not actually enacted here, since that's |
| // done in Blink. |
| expected.set_referrer_url(kReferrerURL); |
| expected.set_did_proceed(false); |
| expected.set_repeat_visit(true); |
| |
| ClientSafeBrowsingReportRequest::Resource* pb_resource = |
| expected.add_resources(); |
| pb_resource->set_id(0); |
| pb_resource->set_url(kLandingURL); |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(1); |
| pb_resource->set_url(kReferrerURL); |
| |
| VerifyResults(actual, expected); |
| } |
| |
| // Tests that a pending load does not interfere with collecting threat details |
| // for the committed page. |
| TEST_F(ThreatDetailsTest, ThreatWithPendingLoad) { |
| const char* kPendingReferrerURL = "http://www.pendingreferrer.com/some/path"; |
| const char* kPendingURL = "http://www.pending.com/some/path"; |
| |
| // Load and commit the landing URL with a referrer. |
| auto navigation = content::NavigationSimulator::CreateBrowserInitiated( |
| GURL(kLandingURL), web_contents()); |
| navigation->SetReferrer(content::Referrer( |
| GURL(kReferrerURL), network::mojom::ReferrerPolicy::kDefault)); |
| navigation->Commit(); |
| |
| // Create UnsafeResource for fake sub-resource of landing page. |
| UnsafeResource resource; |
| InitResource(SB_THREAT_TYPE_URL_MALWARE, ThreatSource::LOCAL_PVER4, |
| true /* is_subresource */, GURL(kThreatURL), &resource); |
| |
| // Start a pending load before creating ThreatDetails. |
| controller().LoadURL( |
| GURL(kPendingURL), |
| content::Referrer(GURL(kPendingReferrerURL), |
| network::mojom::ReferrerPolicy::kDefault), |
| ui::PAGE_TRANSITION_TYPED, std::string()); |
| |
| // Do ThreatDetails collection. |
| auto report = std::make_unique<ThreatDetailsWrap>( |
| ui_manager_.get(), web_contents(), resource, nullptr, history_service(), |
| referrer_chain_provider_.get()); |
| report->StartCollection(); |
| |
| std::string serialized = WaitForThreatDetailsDone( |
| report.get(), true /* did_proceed*/, 1 /* num_visit */); |
| |
| ClientSafeBrowsingReportRequest actual; |
| actual.ParseFromString(serialized); |
| |
| ClientSafeBrowsingReportRequest expected; |
| expected.set_type(ClientSafeBrowsingReportRequest::URL_MALWARE); |
| expected.mutable_client_properties()->set_url_api_type( |
| ClientSafeBrowsingReportRequest::PVER4_NATIVE); |
| expected.set_url(kThreatURL); |
| expected.set_page_url(kLandingURL); |
| // Note that the referrer policy is not actually enacted here, since that's |
| // done in Blink. |
| expected.set_referrer_url(kReferrerURL); |
| expected.set_did_proceed(true); |
| expected.set_repeat_visit(true); |
| |
| ClientSafeBrowsingReportRequest::Resource* pb_resource = |
| expected.add_resources(); |
| pb_resource->set_id(0); |
| pb_resource->set_url(kLandingURL); |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(1); |
| pb_resource->set_url(kThreatURL); |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(2); |
| pb_resource->set_url(kReferrerURL); |
| |
| VerifyResults(actual, expected); |
| } |
| |
| TEST_F(ThreatDetailsTest, ThreatOnFreshTab) { |
| // A fresh WebContents should not have any NavigationEntries yet. (See |
| // https://crbug.com/524208.) |
| EXPECT_EQ(nullptr, controller().GetLastCommittedEntry()); |
| EXPECT_EQ(nullptr, controller().GetPendingEntry()); |
| |
| // Simulate a subresource malware hit (this could happen if the WebContents |
| // was created with window.open, and had content injected into it). |
| UnsafeResource resource; |
| InitResource(SB_THREAT_TYPE_URL_MALWARE, ThreatSource::CLIENT_SIDE_DETECTION, |
| true /* is_subresource */, GURL(kThreatURL), &resource); |
| |
| // Do ThreatDetails collection. |
| auto report = std::make_unique<ThreatDetailsWrap>( |
| ui_manager_.get(), web_contents(), resource, nullptr, history_service(), |
| referrer_chain_provider_.get()); |
| report->StartCollection(); |
| |
| std::string serialized = WaitForThreatDetailsDone( |
| report.get(), true /* did_proceed*/, 1 /* num_visit */); |
| |
| ClientSafeBrowsingReportRequest actual; |
| actual.ParseFromString(serialized); |
| |
| ClientSafeBrowsingReportRequest expected; |
| expected.set_type(ClientSafeBrowsingReportRequest::URL_MALWARE); |
| expected.set_url(kThreatURL); |
| expected.set_did_proceed(true); |
| expected.set_repeat_visit(true); |
| |
| ClientSafeBrowsingReportRequest::Resource* pb_resource = |
| expected.add_resources(); |
| pb_resource->set_id(0); |
| pb_resource->set_url(kThreatURL); |
| |
| VerifyResults(actual, expected); |
| } |
| |
| // Tests the interaction with the HTTP cache. |
| TEST_F(ThreatDetailsTest, HTTPCache) { |
| content::WebContentsTester::For(web_contents()) |
| ->NavigateAndCommit(GURL(kLandingURL)); |
| |
| UnsafeResource resource; |
| InitResource(SB_THREAT_TYPE_URL_CLIENT_SIDE_PHISHING, |
| ThreatSource::CLIENT_SIDE_DETECTION, true /* is_subresource */, |
| GURL(kThreatURL), &resource); |
| |
| auto report = std::make_unique<ThreatDetailsWrap>( |
| ui_manager_.get(), web_contents(), resource, test_shared_loader_factory_, |
| history_service(), referrer_chain_provider_.get()); |
| report->StartCollection(); |
| |
| SimulateFillCache(kThreatURL); |
| |
| // The cache collection starts after the IPC from the DOM is fired. |
| std::vector<mojom::ThreatDOMDetailsNodePtr> params; |
| report->OnReceivedThreatDOMDetails(nullptr, main_rfh(), std::move(params)); |
| |
| // Let the cache callbacks complete. |
| base::RunLoop().RunUntilIdle(); |
| |
| DVLOG(1) << "Getting serialized report"; |
| std::string serialized = WaitForThreatDetailsDone( |
| report.get(), true /* did_proceed*/, -1 /* num_visit */); |
| ClientSafeBrowsingReportRequest actual; |
| actual.ParseFromString(serialized); |
| |
| ClientSafeBrowsingReportRequest expected; |
| expected.set_type(ClientSafeBrowsingReportRequest::URL_CLIENT_SIDE_PHISHING); |
| expected.set_url(kThreatURL); |
| expected.set_page_url(kLandingURL); |
| expected.set_referrer_url(""); |
| expected.set_did_proceed(true); |
| |
| ClientSafeBrowsingReportRequest::Resource* pb_resource = |
| expected.add_resources(); |
| pb_resource->set_id(0); |
| pb_resource->set_url(kLandingURL); |
| ClientSafeBrowsingReportRequest::HTTPResponse* pb_response = |
| pb_resource->mutable_response(); |
| pb_response->mutable_firstline()->set_code(200); |
| ClientSafeBrowsingReportRequest::HTTPHeader* pb_header = |
| pb_response->add_headers(); |
| pb_header->set_name("Content-Type"); |
| pb_header->set_value("text/html"); |
| pb_header = pb_response->add_headers(); |
| pb_header->set_name("Content-Length"); |
| pb_header->set_value("1024"); |
| pb_response->set_body(kLandingData); |
| std::string landing_data(kLandingData); |
| pb_response->set_bodylength(landing_data.size()); |
| pb_response->set_bodydigest(base::MD5String(landing_data)); |
| pb_response->set_remote_ip("1.2.3.4:80"); |
| |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(1); |
| pb_resource->set_url(kThreatURL); |
| pb_response = pb_resource->mutable_response(); |
| pb_response->mutable_firstline()->set_code(200); |
| pb_header = pb_response->add_headers(); |
| pb_header->set_name("Content-Type"); |
| pb_header->set_value("image/jpeg"); |
| pb_header = pb_response->add_headers(); |
| pb_header->set_name("Some-Other-Header"); |
| pb_header->set_value("foo"); |
| pb_response->set_body(kThreatData); |
| std::string threat_data(kThreatData); |
| pb_response->set_bodylength(threat_data.size()); |
| pb_response->set_bodydigest(base::MD5String(threat_data)); |
| pb_response->set_remote_ip("1.2.3.4:80"); |
| expected.set_complete(true); |
| |
| VerifyResults(actual, expected); |
| } |
| |
| // Test that only some fields of the HTTPS resource (eg: whitelisted headers) |
| // are reported. |
| TEST_F(ThreatDetailsTest, HttpsResourceSanitization) { |
| content::WebContentsTester::For(web_contents()) |
| ->NavigateAndCommit(GURL(kLandingURL)); |
| |
| UnsafeResource resource; |
| InitResource(SB_THREAT_TYPE_URL_CLIENT_SIDE_PHISHING, |
| ThreatSource::CLIENT_SIDE_DETECTION, true /* is_subresource */, |
| GURL(kThreatURLHttps), &resource); |
| |
| auto report = std::make_unique<ThreatDetailsWrap>( |
| ui_manager_.get(), web_contents(), resource, test_shared_loader_factory_, |
| history_service(), referrer_chain_provider_.get()); |
| report->StartCollection(); |
| |
| SimulateFillCache(kThreatURLHttps); |
| |
| // The cache collection starts after the IPC from the DOM is fired. |
| std::vector<mojom::ThreatDOMDetailsNodePtr> params; |
| report->OnReceivedThreatDOMDetails(nullptr, main_rfh(), std::move(params)); |
| |
| // Let the cache callbacks complete. |
| base::RunLoop().RunUntilIdle(); |
| |
| DVLOG(1) << "Getting serialized report"; |
| std::string serialized = WaitForThreatDetailsDone( |
| report.get(), true /* did_proceed*/, -1 /* num_visit */); |
| ClientSafeBrowsingReportRequest actual; |
| actual.ParseFromString(serialized); |
| |
| ClientSafeBrowsingReportRequest expected; |
| expected.set_type(ClientSafeBrowsingReportRequest::URL_CLIENT_SIDE_PHISHING); |
| expected.set_url(kThreatURLHttps); |
| expected.set_page_url(kLandingURL); |
| expected.set_referrer_url(""); |
| expected.set_did_proceed(true); |
| |
| ClientSafeBrowsingReportRequest::Resource* pb_resource = |
| expected.add_resources(); |
| pb_resource->set_id(0); |
| pb_resource->set_url(kLandingURL); |
| ClientSafeBrowsingReportRequest::HTTPResponse* pb_response = |
| pb_resource->mutable_response(); |
| pb_response->mutable_firstline()->set_code(200); |
| ClientSafeBrowsingReportRequest::HTTPHeader* pb_header = |
| pb_response->add_headers(); |
| pb_header->set_name("Content-Type"); |
| pb_header->set_value("text/html"); |
| pb_header = pb_response->add_headers(); |
| pb_header->set_name("Content-Length"); |
| pb_header->set_value("1024"); |
| pb_response->set_body(kLandingData); |
| std::string landing_data(kLandingData); |
| pb_response->set_bodylength(landing_data.size()); |
| pb_response->set_bodydigest(base::MD5String(landing_data)); |
| pb_response->set_remote_ip("1.2.3.4:80"); |
| |
| // The threat URL is HTTP so the request and response are cleared (except for |
| // whitelisted headers and certain safe fields). Namely the firstline and body |
| // are missing. |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(1); |
| pb_resource->set_url(kThreatURLHttps); |
| pb_response = pb_resource->mutable_response(); |
| pb_header = pb_response->add_headers(); |
| pb_header->set_name("Content-Type"); |
| pb_header->set_value("image/jpeg"); |
| std::string threat_data(kThreatData); |
| pb_response->set_bodylength(threat_data.size()); |
| pb_response->set_bodydigest(base::MD5String(threat_data)); |
| pb_response->set_remote_ip("1.2.3.4:80"); |
| expected.set_complete(true); |
| |
| VerifyResults(actual, expected); |
| } |
| |
| // Tests the interaction with the HTTP cache (where the cache is empty). |
| TEST_F(ThreatDetailsTest, HTTPCacheNoEntries) { |
| content::WebContentsTester::For(web_contents()) |
| ->NavigateAndCommit(GURL(kLandingURL)); |
| |
| UnsafeResource resource; |
| InitResource(SB_THREAT_TYPE_URL_CLIENT_SIDE_MALWARE, |
| ThreatSource::LOCAL_PVER3, true /* is_subresource */, |
| GURL(kThreatURL), &resource); |
| |
| auto report = std::make_unique<ThreatDetailsWrap>( |
| ui_manager_.get(), web_contents(), resource, test_shared_loader_factory_, |
| history_service(), referrer_chain_provider_.get()); |
| report->StartCollection(); |
| |
| // Simulate no cache entry found. |
| test_url_loader_factory_.AddResponse( |
| GURL(kThreatURL), network::ResourceResponseHead(), std::string(), |
| network::URLLoaderCompletionStatus(net::ERR_CACHE_MISS)); |
| test_url_loader_factory_.AddResponse( |
| GURL(kLandingURL), network::ResourceResponseHead(), std::string(), |
| network::URLLoaderCompletionStatus(net::ERR_CACHE_MISS)); |
| |
| // The cache collection starts after the IPC from the DOM is fired. |
| std::vector<mojom::ThreatDOMDetailsNodePtr> params; |
| report->OnReceivedThreatDOMDetails(nullptr, main_rfh(), std::move(params)); |
| |
| // Let the cache callbacks complete. |
| base::RunLoop().RunUntilIdle(); |
| |
| DVLOG(1) << "Getting serialized report"; |
| std::string serialized = WaitForThreatDetailsDone( |
| report.get(), false /* did_proceed*/, -1 /* num_visit */); |
| ClientSafeBrowsingReportRequest actual; |
| actual.ParseFromString(serialized); |
| |
| ClientSafeBrowsingReportRequest expected; |
| expected.set_type(ClientSafeBrowsingReportRequest::URL_CLIENT_SIDE_MALWARE); |
| expected.mutable_client_properties()->set_url_api_type( |
| ClientSafeBrowsingReportRequest::PVER3_NATIVE); |
| expected.set_url(kThreatURL); |
| expected.set_page_url(kLandingURL); |
| expected.set_referrer_url(""); |
| expected.set_did_proceed(false); |
| |
| ClientSafeBrowsingReportRequest::Resource* pb_resource = |
| expected.add_resources(); |
| pb_resource->set_id(0); |
| pb_resource->set_url(kLandingURL); |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(1); |
| pb_resource->set_url(kThreatURL); |
| expected.set_complete(true); |
| |
| VerifyResults(actual, expected); |
| } |
| |
| // Test getting redirects from history service. |
| TEST_F(ThreatDetailsTest, HistoryServiceUrls) { |
| // Add content to history service. |
| // There are two redirect urls before reacing malware url: |
| // kFirstRedirectURL -> kSecondRedirectURL -> kThreatURL |
| GURL baseurl(kThreatURL); |
| history::RedirectList redirects; |
| redirects.push_back(GURL(kFirstRedirectURL)); |
| redirects.push_back(GURL(kSecondRedirectURL)); |
| AddPageToHistory(baseurl, &redirects); |
| // Wait for history service operation finished. |
| profile()->BlockUntilHistoryProcessesPendingRequests(); |
| |
| content::WebContentsTester::For(web_contents()) |
| ->NavigateAndCommit(GURL(kLandingURL)); |
| |
| UnsafeResource resource; |
| InitResource(SB_THREAT_TYPE_URL_MALWARE, ThreatSource::LOCAL_PVER3, |
| true /* is_subresource */, GURL(kThreatURL), &resource); |
| auto report = std::make_unique<ThreatDetailsWrap>( |
| ui_manager_.get(), web_contents(), resource, nullptr, history_service(), |
| referrer_chain_provider_.get()); |
| report->StartCollection(); |
| |
| // The redirects collection starts after the IPC from the DOM is fired. |
| std::vector<mojom::ThreatDOMDetailsNodePtr> params; |
| report->OnReceivedThreatDOMDetails(nullptr, main_rfh(), std::move(params)); |
| |
| // Let the redirects callbacks complete. |
| base::RunLoop().RunUntilIdle(); |
| |
| std::string serialized = WaitForThreatDetailsDone( |
| report.get(), true /* did_proceed*/, 1 /* num_visit */); |
| ClientSafeBrowsingReportRequest actual; |
| actual.ParseFromString(serialized); |
| |
| ClientSafeBrowsingReportRequest expected; |
| expected.set_type(ClientSafeBrowsingReportRequest::URL_MALWARE); |
| expected.mutable_client_properties()->set_url_api_type( |
| ClientSafeBrowsingReportRequest::PVER3_NATIVE); |
| expected.set_url(kThreatURL); |
| expected.set_page_url(kLandingURL); |
| expected.set_referrer_url(""); |
| expected.set_did_proceed(true); |
| expected.set_repeat_visit(true); |
| |
| ClientSafeBrowsingReportRequest::Resource* pb_resource = |
| expected.add_resources(); |
| pb_resource->set_id(0); |
| pb_resource->set_url(kLandingURL); |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(1); |
| pb_resource->set_parent_id(2); |
| pb_resource->set_url(kThreatURL); |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(2); |
| pb_resource->set_parent_id(3); |
| pb_resource->set_url(kSecondRedirectURL); |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(3); |
| pb_resource->set_url(kFirstRedirectURL); |
| |
| VerifyResults(actual, expected); |
| } |
| |
| } // namespace safe_browsing |