diff --git a/browser/brave_content_browser_client_browsertest.cc b/browser/brave_content_browser_client_browsertest.cc index f4473f4263b2..512674da1df7 100644 --- a/browser/brave_content_browser_client_browsertest.cc +++ b/browser/brave_content_browser_client_browsertest.cc @@ -535,6 +535,18 @@ IN_PROC_BROWSER_TEST_F(BraveContentBrowserClientReferrerTest, &referrer); EXPECT_EQ(referrer->url, kExtensionUrl); + // Special rule for Onion services. + const GURL kOnionUrl("http://lwkjglkejslkgjel.onion/index.html"); + referrer = kReferrer.Clone(); + referrer->url = kOnionUrl; + client()->MaybeHideReferrer(browser()->profile(), kRequestUrl, kOnionUrl, + &referrer); + EXPECT_EQ(referrer->url, GURL()); // .onion -> normal + referrer = kReferrer.Clone(); + client()->MaybeHideReferrer(browser()->profile(), kOnionUrl, kDocumentUrl, + &referrer); + EXPECT_EQ(referrer->url, kDocumentUrl.GetOrigin()); // normal -> .onion + // Allow referrers for certain URL. content_settings()->SetContentSettingCustomScope( ContentSettingsPattern::FromString(kDocumentUrl.GetOrigin().spec() + "*"), diff --git a/browser/net/brave_site_hacks_network_delegate_helper_browsertest.cc b/browser/net/brave_site_hacks_network_delegate_helper_browsertest.cc index db13a74ee084..f95d11173eb3 100644 --- a/browser/net/brave_site_hacks_network_delegate_helper_browsertest.cc +++ b/browser/net/brave_site_hacks_network_delegate_helper_browsertest.cc @@ -9,6 +9,7 @@ #include "base/strings/stringprintf.h" #include "brave/common/brave_paths.h" #include "brave/components/brave_shields/browser/brave_shields_util.h" +#include "brave/components/tor/onion_location_navigation_throttle.h" #include "chrome/browser/content_settings/host_content_settings_map_factory.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" @@ -38,6 +39,10 @@ class BraveSiteHacksNetworkDelegateBrowserTest : public InProcessBrowserTest { https_server_.AddDefaultHandlers(GetChromeTestDataDir()); content::SetupCrossSiteRedirector(&https_server_); + https_server_.RegisterRequestMonitor(base::BindRepeating( + &BraveSiteHacksNetworkDelegateBrowserTest::HandleRequest, + base::Unretained(this))); + ASSERT_TRUE(https_server_.Start()); simple_landing_url_ = https_server_.GetURL("a.com", "/simple.html"); @@ -49,6 +54,39 @@ class BraveSiteHacksNetworkDelegateBrowserTest : public InProcessBrowserTest { cross_site_url_ = https_server_.GetURL("b.com", "/navigate-to-site.html"); same_site_url_ = https_server_.GetURL("sub.a.com", "/navigate-to-site.html"); + + onion_url_ = https_server_.GetURL("foobar.onion", "/navigate-to-site.html"); + onion_post_url_ = + https_server_.GetURL("foobar.onion", "/post-to-site.html"); + reflect_referrer_cross_origin_url_ = + https_server_.GetURL("a.com", "/reflect-referrer.html"); + reflect_referrer_cross_origin_redirect_url_ = https_server_.GetURL( + "foobar.onion", + "/server-redirect-307?" + reflect_referrer_cross_origin_url_.spec()); + reflect_referrer_same_origin_url_ = + https_server_.GetURL("foobar.onion", "/reflect-referrer.html"); + reflect_referrer_same_origin_redirect_url_ = https_server_.GetURL( + "foobar.onion", + "/server-redirect-307?" + reflect_referrer_same_origin_url_.spec()); + images_url_ = https_server_.GetURL("foobar.onion", "/referrer_images.html"); + } + + void HandleRequest(const net::test_server::HttpRequest& request) { + base::AutoLock auto_lock(last_headers_lock_); + + auto referrer_it = request.headers.find("Referer"); + if (referrer_it == request.headers.end()) { + last_referrer_[request.GetURL()] = ""; + } else { + last_referrer_[request.GetURL()] = referrer_it->second; + } + + auto origin_it = request.headers.find("Origin"); + if (origin_it == request.headers.end()) { + last_origin_[request.GetURL()] = ""; + } else { + last_origin_[request.GetURL()] = origin_it->second; + } } HostContentSettingsMap* content_settings() { @@ -61,8 +99,6 @@ class BraveSiteHacksNetworkDelegateBrowserTest : public InProcessBrowserTest { command_line->AppendSwitch(switches::kIgnoreCertificateErrors); } - const net::EmbeddedTestServer& https_server() { return https_server_; } - GURL url(const GURL& destination_url, const GURL& navigation_url) { std::string encoded_destination; base::Base64UrlEncode(destination_url.spec(), @@ -97,6 +133,45 @@ class BraveSiteHacksNetworkDelegateBrowserTest : public InProcessBrowserTest { } const GURL& same_site_url() { return same_site_url_; } + const GURL& onion_url() { return onion_url_; } + const GURL& onion_post_url() { return onion_post_url_; } + const GURL& reflect_referrer_cross_origin_url() { + return reflect_referrer_cross_origin_url_; + } + const GURL& reflect_referrer_cross_origin_redirect_url() { + return reflect_referrer_cross_origin_redirect_url_; + } + const GURL& reflect_referrer_same_origin_url() { + return reflect_referrer_same_origin_url_; + } + const GURL& reflect_referrer_same_origin_redirect_url() { + return reflect_referrer_same_origin_redirect_url_; + } + + const GURL& images_url() { return images_url_; } + GURL image_url(const std::string& number) { + GURL::Replacements replacements; + replacements.SetPathStr("/logo-referrer.png"); + replacements.SetQueryStr(number); + return images_url().ReplaceComponents(replacements); + } + + const std::string& last_referrer(const GURL& url) { + base::AutoLock auto_lock(last_headers_lock_); + GURL::Replacements replacements; + replacements.SetHostStr("127.0.0.1"); + const GURL internal_url = url.ReplaceComponents(replacements); + return last_referrer_[internal_url]; + } + + const std::string& last_origin(const GURL& url) { + base::AutoLock auto_lock(last_headers_lock_); + GURL::Replacements replacements; + replacements.SetHostStr("127.0.0.1"); + const GURL internal_url = url.ReplaceComponents(replacements); + return last_origin_[internal_url]; + } + content::WebContents* contents() { return browser()->tab_strip_model()->GetActiveWebContents(); } @@ -119,8 +194,19 @@ class BraveSiteHacksNetworkDelegateBrowserTest : public InProcessBrowserTest { GURL redirect_to_same_site_landing_url_; GURL same_site_url_; GURL simple_landing_url_; - base::FilePath test_data_dir_; + GURL onion_url_; + GURL onion_post_url_; + GURL reflect_referrer_cross_origin_url_; + GURL reflect_referrer_cross_origin_redirect_url_; + GURL reflect_referrer_same_origin_url_; + GURL reflect_referrer_same_origin_redirect_url_; + GURL images_url_; + std::map last_referrer_; + std::map last_origin_; + mutable base::Lock last_headers_lock_; + + base::FilePath test_data_dir_; net::test_server::EmbeddedTestServer https_server_; }; @@ -256,3 +342,100 @@ IN_PROC_BROWSER_TEST_F(BraveSiteHacksNetworkDelegateBrowserTest, EXPECT_EQ(contents()->GetLastCommittedURL(), output); } } + +IN_PROC_BROWSER_TEST_F(BraveSiteHacksNetworkDelegateBrowserTest, + OnionReferrers) { + // Don't block the mock .onion requests. + tor::OnionLocationNavigationThrottle::BlockOnionRequestsOutsideTorForTesting( + false); + + // Same-origin navigations + { + const GURL dest_url = reflect_referrer_same_origin_url(); + const GURL same_origin_test_url = url(dest_url, onion_url()); + NavigateToURLAndWaitForRedirects(same_origin_test_url, dest_url); + EXPECT_EQ(last_referrer(dest_url), same_origin_test_url.spec()); + EXPECT_EQ(last_origin(dest_url), ""); + + // Redirect + const GURL intermediate_url = reflect_referrer_same_origin_redirect_url(); + const GURL same_origin_redirect_test_url = + url(intermediate_url, onion_url()); + NavigateToURLAndWaitForRedirects(same_origin_redirect_test_url, dest_url); + EXPECT_EQ(last_referrer(dest_url), same_origin_redirect_test_url.spec()); + EXPECT_EQ(last_origin(dest_url), ""); + } + { + // POST + const GURL dest_url = reflect_referrer_same_origin_url(); + const GURL same_origin_test_url = url(dest_url, onion_post_url()); + NavigateToURLAndWaitForRedirects(same_origin_test_url, dest_url); + EXPECT_EQ(last_referrer(dest_url), same_origin_test_url.spec()); + std::string full_origin = same_origin_test_url.GetOrigin().spec(); + full_origin.pop_back(); // CORS headers don't use canonical forms. + EXPECT_EQ(last_origin(dest_url), full_origin); + + // Redirect + const GURL intermediate_url = reflect_referrer_same_origin_redirect_url(); + const GURL same_origin_redirect_test_url = + url(intermediate_url, onion_post_url()); + NavigateToURLAndWaitForRedirects(same_origin_redirect_test_url, dest_url); + EXPECT_EQ(last_referrer(dest_url), same_origin_redirect_test_url.spec()); + EXPECT_EQ(last_origin(dest_url), full_origin); + } + + // Cross-origin navigations + { + const GURL dest_url = reflect_referrer_cross_origin_url(); + NavigateToURLAndWaitForRedirects(url(dest_url, onion_url()), dest_url); + EXPECT_EQ(last_referrer(dest_url), ""); + EXPECT_EQ(last_origin(dest_url), ""); + + // Redirect + const GURL intermediate_url = reflect_referrer_cross_origin_redirect_url(); + NavigateToURLAndWaitForRedirects(url(intermediate_url, onion_url()), + dest_url); + EXPECT_EQ(last_referrer(dest_url), ""); + EXPECT_EQ(last_origin(dest_url), ""); + } + { + // POST + const GURL dest_url = reflect_referrer_cross_origin_url(); + NavigateToURLAndWaitForRedirects(url(dest_url, onion_post_url()), dest_url); + EXPECT_EQ(last_referrer(dest_url), ""); + EXPECT_EQ(last_origin(dest_url), "null"); + + // Redirect + const GURL intermediate_url = reflect_referrer_cross_origin_redirect_url(); + NavigateToURLAndWaitForRedirects(url(intermediate_url, onion_post_url()), + dest_url); + EXPECT_EQ(last_referrer(dest_url), ""); + EXPECT_EQ(last_origin(dest_url), "null"); + } + + NavigateToURLAndWaitForRedirects(images_url(), images_url()); + + // Same-origin sub-requests + std::string full_origin = images_url().GetOrigin().spec(); + full_origin.pop_back(); // CORS headers don't use canonical forms. + EXPECT_EQ(last_referrer(image_url("1")), images_url().spec()); + EXPECT_EQ(last_origin(image_url("1")), ""); // nocors + EXPECT_EQ(last_referrer(image_url("2")), images_url().spec()); + EXPECT_EQ(last_origin(image_url("2")), full_origin); + // Redirects + EXPECT_EQ(last_referrer(image_url("3")), images_url().spec()); + EXPECT_EQ(last_origin(image_url("3")), ""); // nocors + EXPECT_EQ(last_referrer(image_url("4")), images_url().spec()); + EXPECT_EQ(last_origin(image_url("4")), full_origin); + + // Cross-origin sub-requests + EXPECT_EQ(last_referrer(image_url("5")), ""); + EXPECT_EQ(last_origin(image_url("5")), ""); // nocors + EXPECT_EQ(last_referrer(image_url("6")), ""); + EXPECT_EQ(last_origin(image_url("6")), "null"); + // Redirects + EXPECT_EQ(last_referrer(image_url("7")), ""); + EXPECT_EQ(last_origin(image_url("7")), ""); // nocors + EXPECT_EQ(last_referrer(image_url("8")), ""); + EXPECT_EQ(last_origin(image_url("8")), "null"); +} diff --git a/browser/net/brave_site_hacks_network_delegate_helper_unittest.cc b/browser/net/brave_site_hacks_network_delegate_helper_unittest.cc index acb6daa32794..e80b92bf32c5 100644 --- a/browser/net/brave_site_hacks_network_delegate_helper_unittest.cc +++ b/browser/net/brave_site_hacks_network_delegate_helper_unittest.cc @@ -13,6 +13,7 @@ #include "brave/browser/net/url_context.h" #include "brave/common/network_constants.h" #include "net/base/net_errors.h" +#include "net/url_request/url_request_job.h" #include "testing/gtest/include/gtest/gtest.h" using brave::ResponseCallback; @@ -100,6 +101,23 @@ TEST(BraveSiteHacksNetworkDelegateHelperTest, } } +TEST(BraveSiteHacksNetworkDelegateHelperTest, OnionReferrerStripped) { + const GURL original_referrer( + "https://" + "brave4u7jddbv7cyviptqjc7jusxh72uik7zt6adtckl5f4nwy2v72qd.onion/"); + const GURL destination("https://brave.com"); + + // Cross-origin request from a .onion gets empty referrer. + auto url1 = net::URLRequestJob::ComputeReferrerForPolicy( + net::ReferrerPolicy::NEVER_CLEAR, original_referrer, destination); + EXPECT_EQ(url1, GURL()); + + // Cross-origin request to a .onion gets normal referrer. + auto url2 = net::URLRequestJob::ComputeReferrerForPolicy( + net::ReferrerPolicy::NEVER_CLEAR, destination, original_referrer); + EXPECT_EQ(url2, destination.GetOrigin()); +} + TEST(BraveSiteHacksNetworkDelegateHelperTest, QueryStringUntouched) { const std::vector urls({ "https://example.com/", diff --git a/chromium_src/net/url_request/url_request_job.cc b/chromium_src/net/url_request/url_request_job.cc new file mode 100644 index 000000000000..e90c496f60d0 --- /dev/null +++ b/chromium_src/net/url_request/url_request_job.cc @@ -0,0 +1,26 @@ +/* Copyright 2021 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "net/url_request/url_request_job.h" + +// Strip referrer for cross-origin requests from a .onion hostname. +// This also affects the Origin header outside of CORS requests. +#define ComputeReferrerForPolicy \ + ComputeReferrerForPolicy( \ + ReferrerPolicy policy, const GURL& original_referrer, \ + const GURL& destination, bool* same_origin_out_for_metrics) { \ + if (base::EndsWith(original_referrer.host_piece(), ".onion", \ + base::CompareCase::INSENSITIVE_ASCII) && \ + !url::IsSameOriginWith(original_referrer, destination)) { \ + return GURL(); \ + } \ + return ComputeReferrerForPolicy_Chromium( \ + policy, original_referrer, destination, same_origin_out_for_metrics); \ + } \ + GURL URLRequestJob::ComputeReferrerForPolicy_Chromium + +#include "../../../../net/url_request/url_request_job.cc" + +#undef ComputeReferrerForPolicy diff --git a/chromium_src/net/url_request/url_request_job.h b/chromium_src/net/url_request/url_request_job.h new file mode 100644 index 000000000000..77bca94dd3ee --- /dev/null +++ b/chromium_src/net/url_request/url_request_job.h @@ -0,0 +1,19 @@ +/* Copyright 2021 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_CHROMIUM_SRC_NET_URL_REQUEST_URL_REQUEST_JOB_H_ +#define BRAVE_CHROMIUM_SRC_NET_URL_REQUEST_URL_REQUEST_JOB_H_ + +#define ComputeReferrerForPolicy \ + ComputeReferrerForPolicy( \ + ReferrerPolicy policy, const GURL& original_referrer, \ + const GURL& destination, bool* same_origin_out_for_metrics = nullptr); \ + static GURL ComputeReferrerForPolicy_Chromium + +#include "../../../../net/url_request/url_request_job.h" + +#undef ComputeReferrerForPolicy + +#endif // BRAVE_CHROMIUM_SRC_NET_URL_REQUEST_URL_REQUEST_JOB_H_ diff --git a/chromium_src/services/network/cors/DEPS b/chromium_src/services/network/cors/DEPS new file mode 100644 index 000000000000..80c2ed7be20c --- /dev/null +++ b/chromium_src/services/network/cors/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+../../../../../services/network/cors", +] diff --git a/chromium_src/services/network/cors/cors_url_loader.cc b/chromium_src/services/network/cors/cors_url_loader.cc new file mode 100644 index 000000000000..686d7f67964b --- /dev/null +++ b/chromium_src/services/network/cors/cors_url_loader.cc @@ -0,0 +1,18 @@ +/* Copyright 2021 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +// Nullify the Origin header for cross-origin CORS requests +// originating from a .onion address. +#define BRAVE_CORS_URL_LOADER_START_REQUEST \ + if (base::EndsWith(request_.request_initiator->host(), ".onion", \ + base::CompareCase::INSENSITIVE_ASCII) && \ + !request_.request_initiator->IsSameOriginWith( \ + url::Origin::Create(request_.url))) { \ + request_.headers.SetHeader(net::HttpRequestHeaders::kOrigin, \ + url::Origin().Serialize()); \ + } else /* NOLINT */ + +#include "../../../../../services/network/cors/cors_url_loader.cc" +#undef BRAVE_CORS_URL_LOADER_START_REQUEST diff --git a/components/tor/onion_location_navigation_throttle.cc b/components/tor/onion_location_navigation_throttle.cc index be2a4631f004..7bff666be211 100644 --- a/components/tor/onion_location_navigation_throttle.cc +++ b/components/tor/onion_location_navigation_throttle.cc @@ -35,6 +35,9 @@ bool GetOnionLocation(const net::HttpResponseHeaders* headers, } // namespace +bool OnionLocationNavigationThrottle:: + block_onion_requests_outside_tor_for_testing_ = true; + // static std::unique_ptr OnionLocationNavigationThrottle::MaybeCreateThrottleFor( @@ -106,7 +109,9 @@ OnionLocationNavigationThrottle::WillStartRequest() { OnionLocationTabHelper::SetOnionLocation( navigation_handle()->GetWebContents(), url); } - return content::NavigationThrottle::BLOCK_REQUEST; + return block_onion_requests_outside_tor_for_testing_ + ? content::NavigationThrottle::BLOCK_REQUEST + : content::NavigationThrottle::PROCEED; } else { OnionLocationTabHelper::SetOnionLocation( navigation_handle()->GetWebContents(), GURL()); diff --git a/components/tor/onion_location_navigation_throttle.h b/components/tor/onion_location_navigation_throttle.h index 6d3d5d09fcec..b63f0fb5a676 100644 --- a/components/tor/onion_location_navigation_throttle.h +++ b/components/tor/onion_location_navigation_throttle.h @@ -44,7 +44,12 @@ class OnionLocationNavigationThrottle : public content::NavigationThrottle { ThrottleCheckResult WillStartRequest() override; const char* GetNameForLogging() override; + static void BlockOnionRequestsOutsideTorForTesting(bool block) { + block_onion_requests_outside_tor_for_testing_ = block; + } + private: + static bool block_onion_requests_outside_tor_for_testing_; bool is_tor_profile_ = false; PrefService* pref_service_ = nullptr; diff --git a/patches/services-network-cors-cors_url_loader.cc.patch b/patches/services-network-cors-cors_url_loader.cc.patch new file mode 100644 index 000000000000..40f708a6c2dc --- /dev/null +++ b/patches/services-network-cors-cors_url_loader.cc.patch @@ -0,0 +1,12 @@ +diff --git a/services/network/cors/cors_url_loader.cc b/services/network/cors/cors_url_loader.cc +index dbacb3e96d1f46a6e5eb5080c69a7bb67058e27c..abb62f7cab19343998c3cd9d313348a5b0220bc6 100644 +--- a/services/network/cors/cors_url_loader.cc ++++ b/services/network/cors/cors_url_loader.cc +@@ -528,6 +528,7 @@ void CorsURLLoader::StartRequest() { + (fetch_cors_flag_ || + (request_.method != net::HttpRequestHeaders::kGetMethod && + request_.method != net::HttpRequestHeaders::kHeadMethod))) { ++ BRAVE_CORS_URL_LOADER_START_REQUEST + if (tainted_) { + request_.headers.SetHeader(net::HttpRequestHeaders::kOrigin, + url::Origin().Serialize()); diff --git a/test/BUILD.gn b/test/BUILD.gn index 9b56cc4210a9..20e4604e2210 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -745,6 +745,7 @@ if (!is_android) { "//brave/components/debounce/browser", "//brave/components/debounce/common", "//brave/components/resources:strings_grit", + "//brave/components/tor", "//brave/renderer/test:browser_tests", "//brave/vendor/bat-native-ads", "//brave/vendor/bat-native-ledger", diff --git a/test/data/logo-referrer.png b/test/data/logo-referrer.png new file mode 100644 index 000000000000..44ddb41fe95a Binary files /dev/null and b/test/data/logo-referrer.png differ diff --git a/test/data/logo-referrer.png.mock-http-headers b/test/data/logo-referrer.png.mock-http-headers new file mode 100644 index 000000000000..2af9f597cfb8 --- /dev/null +++ b/test/data/logo-referrer.png.mock-http-headers @@ -0,0 +1,4 @@ +HTTP/1.1 200 OK +Cache-Control: no-store +Content-Type: image/png +Access-Control-Allow-Origin: * diff --git a/test/data/post-to-site.html b/test/data/post-to-site.html new file mode 100644 index 000000000000..0e7a0275f9d6 --- /dev/null +++ b/test/data/post-to-site.html @@ -0,0 +1,19 @@ + + + + +

+ +

+

Waiting for JS form submission...

+ + diff --git a/test/data/referrer_images.html b/test/data/referrer_images.html new file mode 100644 index 000000000000..de5f7ba8b1be --- /dev/null +++ b/test/data/referrer_images.html @@ -0,0 +1,39 @@ + + + + + + + + +
+ + + +> + + + diff --git a/test/data/reflect-referrer.html b/test/data/reflect-referrer.html new file mode 100644 index 000000000000..ae89db32c975 --- /dev/null +++ b/test/data/reflect-referrer.html @@ -0,0 +1,9 @@ + + +

Referrer (JS):

+ + + diff --git a/test/data/reflect-referrer.html.mock-http-headers b/test/data/reflect-referrer.html.mock-http-headers new file mode 100644 index 000000000000..bac4a0b1bfa6 --- /dev/null +++ b/test/data/reflect-referrer.html.mock-http-headers @@ -0,0 +1,3 @@ +HTTP/1.1 200 OK +Cache-Control: no-store +Content-Type: text/html