Skip to content

Commit

Permalink
Merge pull request #1744 from brave/hsts_fingerprinting
Browse files Browse the repository at this point in the history
Issue 3419: Mitigate HSTS fingerprinting
  • Loading branch information
jumde authored Mar 1, 2019
2 parents 96a8582 + 80eaac6 commit 680cb0f
Show file tree
Hide file tree
Showing 8 changed files with 295 additions and 6 deletions.
49 changes: 45 additions & 4 deletions browser/net/brave_network_delegate_base.cc
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
/* Copyright (c) 2019 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 http://mozilla.org/MPL/2.0/. */

#include "brave/browser/net/brave_network_delegate_base.h"

#include <algorithm>
#include <utility>

#include "base/task/post_task.h"
#include "brave/common/pref_names.h"
Expand All @@ -19,9 +21,11 @@
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "net/url_request/url_request.h"

using content::BrowserThread;
using net::HttpResponseHeaders;
using net::URLRequest;

namespace {
Expand All @@ -40,6 +44,41 @@ content::WebContents* GetWebContentsFromProcessAndFrameId(int render_process_id,

} // namespace

base::flat_set<base::StringPiece>* TrackableSecurityHeaders() {
static base::NoDestructor<base::flat_set<base::StringPiece>>
kTrackableSecurityHeaders(base::flat_set<base::StringPiece>{
"Strict-Transport-Security", "Expect-CT", "Public-Key-Pins",
"Public-Key-Pins-Report-Only"});
return kTrackableSecurityHeaders.get();
}

void RemoveTrackableSecurityHeadersForThirdParty(
URLRequest* request,
const net::HttpResponseHeaders* original_response_headers,
scoped_refptr<net::HttpResponseHeaders>* override_response_headers) {
if (!request || !request->top_frame_origin().has_value() ||
(!original_response_headers && !override_response_headers->get())) {
return;
}

auto top_frame_origin = request->top_frame_origin().value();
auto request_url = request->url();

if (net::registry_controlled_domains::SameDomainOrHost(
request_url, top_frame_origin,
net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES)) {
return;
}

if (!override_response_headers->get()) {
*override_response_headers =
new net::HttpResponseHeaders(original_response_headers->raw_headers());
}
for (auto header : *TrackableSecurityHeaders()) {
(*override_response_headers)->RemoveHeader(header.as_string());
}
}

BraveNetworkDelegateBase::BraveNetworkDelegateBase(
extensions::EventRouterForwarder* event_router)
: ChromeNetworkDelegate(event_router), referral_headers_list_(nullptr) {
Expand Down Expand Up @@ -68,12 +107,11 @@ void BraveNetworkDelegateBase::InitPrefChangeRegistrarOnUI() {
void BraveNetworkDelegateBase::OnReferralHeadersChanged() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (const base::ListValue* referral_headers =
g_browser_process->local_state()->GetList(kReferralHeaders)) {
g_browser_process->local_state()->GetList(kReferralHeaders)) {
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::IO},
base::Bind(&BraveNetworkDelegateBase::SetReferralHeaders,
base::Unretained(this),
referral_headers->DeepCopy()));
base::Unretained(this), referral_headers->DeepCopy()));
}
}

Expand Down Expand Up @@ -124,6 +162,9 @@ int BraveNetworkDelegateBase::OnHeadersReceived(
const net::HttpResponseHeaders* original_response_headers,
scoped_refptr<net::HttpResponseHeaders>* override_response_headers,
GURL* allowed_unsafe_redirect_url) {
RemoveTrackableSecurityHeadersForThirdParty(
request, original_response_headers, override_response_headers);

if (headers_received_callbacks_.empty() || !request) {
return ChromeNetworkDelegate::OnHeadersReceived(
request, std::move(callback), original_response_headers,
Expand Down
20 changes: 18 additions & 2 deletions browser/net/brave_network_delegate_base.h
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
/* Copyright (c) 2019 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 http://mozilla.org/MPL/2.0/. */

#ifndef BRAVE_BROWSER_NET_BRAVE_NETWORK_DELEGATE_BASE_H_
#define BRAVE_BROWSER_NET_BRAVE_NETWORK_DELEGATE_BASE_H_

#include <map>
#include <memory>
#include <string>
#include <vector>

#include "base/containers/flat_set.h"
#include "base/strings/string_piece.h"
#include "brave/browser/net/url_context.h"
#include "chrome/browser/net/chrome_network_delegate.h"
#include "content/public/browser/browser_thread.h"
Expand All @@ -20,6 +28,13 @@ namespace net {
class URLRequest;
}

base::flat_set<base::StringPiece>* TrackableSecurityHeaders();

void RemoveTrackableSecurityHeadersForThirdParty(
net::URLRequest* request,
const net::HttpResponseHeaders* original_response_headers,
scoped_refptr<net::HttpResponseHeaders>* override_response_headers);

// BraveNetworkDelegateBase is the central point from within the Brave code to
// add hooks into the network stack.
class BraveNetworkDelegateBase : public ChromeNetworkDelegate {
Expand All @@ -28,7 +43,8 @@ class BraveNetworkDelegateBase : public ChromeNetworkDelegate {
using ResponseListener = base::Callback<void(const base::DictionaryValue&,
const ResponseCallback&)>;

BraveNetworkDelegateBase(extensions::EventRouterForwarder* event_router);
explicit BraveNetworkDelegateBase(
extensions::EventRouterForwarder* event_router);
~BraveNetworkDelegateBase() override;

bool IsRequestIdentifierValid(uint64_t request_identifier);
Expand Down
117 changes: 117 additions & 0 deletions browser/net/brave_network_delegate_base_unittest.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/* Copyright (c) 2019 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 http://mozilla.org/MPL/2.0/. */

#include "brave/browser/net/brave_network_delegate_base.h"

#include <string>

#include "brave/browser/net/url_context.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "net/url_request/url_request_test_util.h"
#include "url/gurl.h"

using net::HttpResponseHeaders;

namespace {

const char kFirstPartyDomain[] = "http://firstparty.com/";
const char kThirdPartyDomain[] = "http://thirdparty.com/";
const char kAcceptLanguageHeader[] = "Accept-Language";
const char kXSSProtectionHeader[] = "X-XSS-Protection";

const char kRawHeaders[] =
"HTTP/1.0 200 OK\n"
"Strict-Transport-Security: max-age=31557600\n"
"Accept-Language: *\n"
"Expect-CT: max-age=86400, enforce "
"report-uri=\"https://foo.example/report\"\n"
"Public-Key-Pins:"
"pin-sha256=\"cUPcTAZWKaASuYWhhBAkE3h2+soZS7sWs=\""
"max-age=5184000; includeSubDomains\n"
"Public-Key-Pins-Report-Only:"
"pin-sha256=\"cUPcTAZWKaASuYWhhBAkE3h2+soZS7sWs=\""
"max-age=5184000; includeSubDomains"
"report-uri=\"https://www.pkp.org/hpkp-report\"\n"
"X-XSS-Protection: 0";

class BraveNetworkDelegateBaseTest : public testing::Test {
public:
BraveNetworkDelegateBaseTest()
: thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP),
context_(new net::TestURLRequestContext(true)) {}
~BraveNetworkDelegateBaseTest() override {}
void SetUp() override { context_->Init(); }
net::TestURLRequestContext* context() { return context_.get(); }

private:
content::TestBrowserThreadBundle thread_bundle_;
std::unique_ptr<net::TestURLRequestContext> context_;
};

TEST_F(BraveNetworkDelegateBaseTest, RemoveTrackableSecurityHeaders) {
net::TestDelegate test_delegate;
GURL request_url(kThirdPartyDomain);
GURL tab_url(kFirstPartyDomain);
std::unique_ptr<net::URLRequest> request = context()->CreateRequest(
request_url, net::IDLE, &test_delegate, TRAFFIC_ANNOTATION_FOR_TESTS);

request->set_top_frame_origin(url::Origin::Create(tab_url));

scoped_refptr<HttpResponseHeaders> headers(
new HttpResponseHeaders(net::HttpUtil::AssembleRawHeaders(
kRawHeaders, strnlen(kRawHeaders, sizeof kRawHeaders))));

RemoveTrackableSecurityHeadersForThirdParty(request.get(), nullptr, &headers);
for (auto header : *TrackableSecurityHeaders()) {
EXPECT_FALSE(headers->HasHeader(header.as_string()));
}
EXPECT_TRUE(headers->HasHeader(kAcceptLanguageHeader));
EXPECT_TRUE(headers->HasHeader(kXSSProtectionHeader));
}

TEST_F(BraveNetworkDelegateBaseTest, RemoveTrackableSecurityHeadersMixedCase) {
net::TestDelegate test_delegate;
GURL request_url(kThirdPartyDomain);
GURL tab_url(kFirstPartyDomain);
std::unique_ptr<net::URLRequest> request = context()->CreateRequest(
request_url, net::IDLE, &test_delegate, TRAFFIC_ANNOTATION_FOR_TESTS);

request->set_top_frame_origin(url::Origin::Create(tab_url));

scoped_refptr<HttpResponseHeaders> headers(
new HttpResponseHeaders(net::HttpUtil::AssembleRawHeaders(
kRawHeaders, strnlen(kRawHeaders, sizeof kRawHeaders))));

RemoveTrackableSecurityHeadersForThirdParty(request.get(), nullptr, &headers);
for (auto header : *TrackableSecurityHeaders()) {
EXPECT_FALSE(headers->HasHeader(header.as_string()));
}
EXPECT_TRUE(headers->HasHeader(kAcceptLanguageHeader));
EXPECT_TRUE(headers->HasHeader(kXSSProtectionHeader));
}

TEST_F(BraveNetworkDelegateBaseTest, RetainTrackableSecurityHeaders) {
net::TestDelegate test_delegate;
GURL request_url(kFirstPartyDomain);
GURL tab_url(kFirstPartyDomain);
std::unique_ptr<net::URLRequest> request = context()->CreateRequest(
request_url, net::IDLE, &test_delegate, TRAFFIC_ANNOTATION_FOR_TESTS);

request->set_top_frame_origin(url::Origin::Create(tab_url));

scoped_refptr<HttpResponseHeaders> headers(
new HttpResponseHeaders(net::HttpUtil::AssembleRawHeaders(
kRawHeaders, strnlen(kRawHeaders, sizeof kRawHeaders))));

RemoveTrackableSecurityHeadersForThirdParty(request.get(), nullptr, &headers);
for (auto header : *TrackableSecurityHeaders()) {
EXPECT_TRUE(headers->HasHeader(header.as_string()));
}
EXPECT_TRUE(headers->HasHeader(kAcceptLanguageHeader));
EXPECT_TRUE(headers->HasHeader(kXSSProtectionHeader));
}

} // namespace
103 changes: 103 additions & 0 deletions browser/net/brave_network_delegate_hsts_fingerprinting_browsertest.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/* Copyright (c) 2019 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 http://mozilla.org/MPL/2.0/. */

#include "base/path_service.h"
#include "brave/common/brave_paths.h"
#include "brave/components/brave_shields/common/brave_shield_constants.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/test/browser_test_utils.h"
#include "net/dns/mock_host_resolver.h"

using content::NavigationHandle;
using content::WebContents;
using content::WebContentsObserver;

class RedirectObserver : public WebContentsObserver {
public:
explicit RedirectObserver(WebContents* web_contents)
: WebContentsObserver(web_contents) {}
~RedirectObserver() override = default;

void DidFinishNavigation(NavigationHandle* handle) override {
const net::HttpResponseHeaders* response = handle->GetResponseHeaders();
if (response) {
const bool has_sts_header =
response->HasHeader("Strict-Transport-Security");
sts_header_for_url_.insert(
std::pair<GURL, bool>(handle->GetURL(), has_sts_header));
}
}

bool has_sts_header(GURL url) const {
auto iter = sts_header_for_url_.find(url);
DCHECK(iter != sts_header_for_url_.end());
return iter->second;
}

private:
std::map<GURL, bool> sts_header_for_url_;

DISALLOW_COPY_AND_ASSIGN(RedirectObserver);
};

class BraveNetworkDelegateBaseBrowserTest : public InProcessBrowserTest {
public:
void SetUpOnMainThread() override {
InProcessBrowserTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");

brave::RegisterPathProvider();
base::FilePath test_data_dir;
base::PathService::Get(brave::DIR_TEST_DATA, &test_data_dir);
embedded_test_server()->ServeFilesFromDirectory(test_data_dir);

ASSERT_TRUE(embedded_test_server()->Start());
first_party_pattern_ = ContentSettingsPattern::FromString("http://a.com/*");
iframe_pattern_ = ContentSettingsPattern::FromString("http://c.com/*");
}

content::WebContents* active_contents() {
return browser()->tab_strip_model()->GetActiveWebContents();
}

HostContentSettingsMap* content_settings() {
return HostContentSettingsMapFactory::GetForProfile(browser()->profile());
}

private:
ContentSettingsPattern first_party_pattern_;
ContentSettingsPattern iframe_pattern_;
};

IN_PROC_BROWSER_TEST_F(BraveNetworkDelegateBaseBrowserTest, FirstPartySTS) {
const GURL third_party =
embedded_test_server()->GetURL("c.com", "/iframe_hsts.html");

RedirectObserver redirect_observer(active_contents());
ui_test_utils::NavigateToURL(browser(), third_party);

EXPECT_TRUE(redirect_observer.has_sts_header(third_party));
}

IN_PROC_BROWSER_TEST_F(BraveNetworkDelegateBaseBrowserTest, ThirdPartySTS) {
const GURL third_party =
embedded_test_server()->GetURL("c.com", "/iframe_hsts.html");
const GURL first_party =
embedded_test_server()->GetURL("a.com", "/hsts.html");

RedirectObserver redirect_observer(active_contents());
ui_test_utils::NavigateToURL(browser(), first_party);

EXPECT_FALSE(redirect_observer.has_sts_header(third_party));
}
2 changes: 2 additions & 0 deletions test/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ test("brave_unit_tests") {
"//brave/browser/net/brave_ad_block_tp_network_delegate_helper_unittest.cc",
"//brave/browser/net/brave_common_static_redirect_network_delegate_helper_unittest.cc",
"//brave/browser/net/brave_httpse_network_delegate_helper_unittest.cc",
"//brave/browser/net/brave_network_delegate_base_unittest.cc",
"//brave/browser/net/brave_referrals_network_delegate_helper_unittest.cc",
"//brave/browser/net/brave_site_hacks_network_delegate_helper_unittest.cc",
"//brave/browser/net/brave_static_redirect_network_delegate_helper_unittest.cc",
Expand Down Expand Up @@ -285,6 +286,7 @@ test("brave_browser_tests") {
"//brave/browser/extensions/api/brave_shields_api_browsertest.cc",
"//brave/browser/extensions/api/brave_theme_api_browsertest.cc",
"//brave/browser/net/brave_network_delegate_browsertest.cc",
"//brave/browser/net/brave_network_delegate_hsts_fingerprinting_browsertest.cc",
"//brave/browser/renderer_context_menu/brave_mock_render_view_context_menu.cc",
"//brave/browser/renderer_context_menu/brave_mock_render_view_context_menu.h",
"//brave/browser/renderer_context_menu/brave_spelling_menu_observer_browsertest.cc",
Expand Down
5 changes: 5 additions & 0 deletions test/data/hsts.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<html><head><title>Third Party HSTS</title></head>
<body>
<iframe src="/cross-site/c.com/iframe_hsts.html"></iframe>
</body></html>

1 change: 1 addition & 0 deletions test/data/iframe_hsts.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello World!
4 changes: 4 additions & 0 deletions test/data/iframe_hsts.html.mock-http-headers
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/html; charset=ISO-8859-1
Strict-Transport-Security: max-age=123; includeSubdomains

0 comments on commit 680cb0f

Please sign in to comment.