diff options
author | tsepez@chromium.org <tsepez@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-05-13 16:45:12 +0000 |
---|---|---|
committer | tsepez@chromium.org <tsepez@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-05-13 16:45:12 +0000 |
commit | 2fb95db2941727bb10a6eedaee3a1bef0af00a1c (patch) | |
tree | db6ada547274d0ccb5a571ced56f6283d97f5f05 | |
parent | 6a8f51186bb732bbeb40ef39eb87fb2ba7d882bb (diff) | |
download | chromium_src-2fb95db2941727bb10a6eedaee3a1bef0af00a1c.zip chromium_src-2fb95db2941727bb10a6eedaee3a1bef0af00a1c.tar.gz chromium_src-2fb95db2941727bb10a6eedaee3a1bef0af00a1c.tar.bz2 |
Block HTTP basic auth from cross-orgin third-party content.
BUG=81251
TEST=browser_tests
Review URL: http://codereview.chromium.org/6918001
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@85281 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/browser/ui/login/login_prompt_browsertest.cc | 68 | ||||
-rw-r--r-- | chrome/common/chrome_switches.cc | 4 | ||||
-rw-r--r-- | chrome/common/chrome_switches.h | 1 | ||||
-rw-r--r-- | chrome/test/data/login/load_img_from_b.html | 14 | ||||
-rw-r--r-- | content/browser/renderer_host/resource_dispatcher_host.cc | 44 |
5 files changed, 131 insertions, 0 deletions
diff --git a/chrome/browser/ui/login/login_prompt_browsertest.cc b/chrome/browser/ui/login/login_prompt_browsertest.cc index d8b29d3..ba2bd8c 100644 --- a/chrome/browser/ui/login/login_prompt_browsertest.cc +++ b/chrome/browser/ui/login/login_prompt_browsertest.cc @@ -17,6 +17,7 @@ #include "content/browser/renderer_host/resource_dispatcher_host.h" #include "content/common/notification_service.h" #include "net/base/auth.h" +#include "net/base/mock_host_resolver.h" namespace { @@ -474,4 +475,71 @@ IN_PROC_BROWSER_TEST_F(LoginPromptBrowserTest, NoLoginPromptForFavicon) { EXPECT_TRUE(test_server()->Stop()); } +// Block crossdomain subresource login prompting as a phishing defense. +IN_PROC_BROWSER_TEST_F(LoginPromptBrowserTest, BlockCrossdomainPrompt) { + const char* kTestPage = "files/login/load_img_from_b.html"; + + host_resolver()->AddRule("www.a.com", "127.0.0.1"); + host_resolver()->AddRule("www.b.com", "127.0.0.1"); + ASSERT_TRUE(test_server()->Start()); + + TabContentsWrapper* contents = browser()->GetSelectedTabContentsWrapper(); + ASSERT_TRUE(contents); + + NavigationController* controller = &contents->controller(); + LoginPromptBrowserTestObserver observer; + observer.Register(Source<NavigationController>(controller)); + + // Load a page that has a cross-domain sub-resource authentication. + // There should be no login prompt. + { + GURL test_page = test_server()->GetURL(kTestPage); + ASSERT_EQ("127.0.0.1", test_page.host()); + + // Change the host from 127.0.0.1 to www.a.com so that when the + // page tries to load from b, it will be cross-origin. + std::string new_host("www.a.com"); + GURL::Replacements replacements; + replacements.SetHostStr(new_host); + test_page = test_page.ReplaceComponents(replacements); + + WindowedLoadStopObserver load_stop_waiter(controller); + browser()->OpenURL(test_page, GURL(), CURRENT_TAB, PageTransition::TYPED); + load_stop_waiter.Wait(); + } + + EXPECT_EQ(0, observer.auth_needed_count_); + + // Now request the same page, but from the same origin. + // There should be one login prompt. + { + GURL test_page = test_server()->GetURL(kTestPage); + ASSERT_EQ("127.0.0.1", test_page.host()); + + // Change the host from 127.0.0.1 to www.b.com so that when the + // page tries to load from b, it will be same-origin. + std::string new_host("www.b.com"); + GURL::Replacements replacements; + replacements.SetHostStr(new_host); + test_page = test_page.ReplaceComponents(replacements); + + WindowedAuthNeededObserver auth_needed_waiter(controller); + browser()->OpenURL(test_page, GURL(), CURRENT_TAB, PageTransition::TYPED); + auth_needed_waiter.Wait(); + ASSERT_EQ(1u, observer.handlers_.size()); + + while (!observer.handlers_.empty()) { + WindowedAuthCancelledObserver auth_cancelled_waiter(controller); + LoginHandler* handler = *observer.handlers_.begin(); + + ASSERT_TRUE(handler); + handler->CancelAuth(); + auth_cancelled_waiter.Wait(); + } + } + + EXPECT_EQ(1, observer.auth_needed_count_); + EXPECT_TRUE(test_server()->Stop()); +} + } // namespace diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc index 253dd26..1f58615 100644 --- a/chrome/common/chrome_switches.cc +++ b/chrome/common/chrome_switches.cc @@ -18,6 +18,10 @@ namespace switches { // is launched on the command line (e.g. by Selenium). Only needed on Mac. const char kActivateOnLaunch[] = "activate-on-launch"; +// Allow third party content included on a page to prompt for a HTTP +// basic auth username/password pair. +const char kAllowCrossOriginAuthPrompt[] = "allow-cross-origin-auth-prompt"; + // On ChromeOS, file:// access is disabled except for certain whitelisted // directories. This switch re-enables file:// for testing. const char kAllowFileAccess[] = "allow-file-access"; diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h index e7e4721..c4876d7 100644 --- a/chrome/common/chrome_switches.h +++ b/chrome/common/chrome_switches.h @@ -24,6 +24,7 @@ namespace switches { // All switches in alphabetical order. The switches should be documented // alongside the definition of their values in the .cc file. extern const char kActivateOnLaunch[]; +extern const char kAllowCrossOriginAuthPrompt[]; extern const char kAllowFileAccess[]; extern const char kAllowOutdatedPlugins[]; extern const char kAllowHTTPBackgroundPage[]; diff --git a/chrome/test/data/login/load_img_from_b.html b/chrome/test/data/login/load_img_from_b.html new file mode 100644 index 0000000..96b93fc --- /dev/null +++ b/chrome/test/data/login/load_img_from_b.html @@ -0,0 +1,14 @@ +<html> +<body> +This page will attempt to load an image requiring basic auth from the +domain www.b.com using the same port that it loaded (since we assume www.b.com +is an alias for this same server). + +<script> +var img = document.createElement("img"); +img.src = "http://www.b.com:" + location.port + "/auth-basic/foo.jpg"; +document.body.appendChild(img); +</script> +</body> +</html> + diff --git a/content/browser/renderer_host/resource_dispatcher_host.cc b/content/browser/renderer_host/resource_dispatcher_host.cc index fac9a705..aeee6e6 100644 --- a/content/browser/renderer_host/resource_dispatcher_host.cc +++ b/content/browser/renderer_host/resource_dispatcher_host.cc @@ -68,6 +68,7 @@ #include "net/base/load_flags.h" #include "net/base/mime_util.h" #include "net/base/net_errors.h" +#include "net/base/registry_controlled_domain.h" #include "net/base/request_priority.h" #include "net/base/ssl_cert_request_info.h" #include "net/base/upload_data.h" @@ -236,6 +237,32 @@ void RemoveDownloadFileFromChildSecurityPolicy(int child_id, #pragma warning(default: 4748) #endif +// Relationship of resource being authenticated with the top level page. +enum HttpAuthResourceType { + HTTP_AUTH_RESOURCE_TOP, // Top-level page itself + HTTP_AUTH_RESOURCE_SAME_DOMAIN, // Sub-content from same domain + HTTP_AUTH_RESOURCE_BLOCKED_CROSS, // Blocked Sub-content from cross domain + HTTP_AUTH_RESOURCE_ALLOWED_CROSS, // Allowed Sub-content per command line + HTTP_AUTH_RESOURCE_LAST +}; + +HttpAuthResourceType HttpAuthResourceTypeOf(net::URLRequest* request) { + // Use the same critera as for cookies to determine the sub-resource type + // that is requesting to be authenticated. + if (!request->first_party_for_cookies().is_valid()) + return HTTP_AUTH_RESOURCE_TOP; + + if (net::RegistryControlledDomainService::SameDomainOrHost( + request->first_party_for_cookies(), request->url())) + return HTTP_AUTH_RESOURCE_SAME_DOMAIN; + + if (CommandLine::ForCurrentProcess()->HasSwitch( + switches::kAllowCrossOriginAuthPrompt)) + return HTTP_AUTH_RESOURCE_ALLOWED_CROSS; + + return HTTP_AUTH_RESOURCE_BLOCKED_CROSS; +} + } // namespace ResourceDispatcherHost::ResourceDispatcherHost( @@ -1089,6 +1116,23 @@ void ResourceDispatcherHost::OnAuthRequired( request->CancelAuth(); return; } + + // Prevent third-party content from prompting for login, unless it is + // a proxy that is trying to authenticate. This is often the foundation + // of a scam to extract credentials for another domain from the user. + if (!auth_info->is_proxy) { + HttpAuthResourceType resource_type = HttpAuthResourceTypeOf(request); + UMA_HISTOGRAM_ENUMERATION("Net.HttpAuthResource", + resource_type, + HTTP_AUTH_RESOURCE_LAST); + + if (resource_type == HTTP_AUTH_RESOURCE_BLOCKED_CROSS) { + request->CancelAuth(); + return; + } + } + + // Create a login dialog on the UI thread to get authentication data, // or pull from cache and continue on the IO thread. // TODO(mpcomplete): We should block the parent tab while waiting for |