| // Copyright (c) 2013 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 "base/command_line.h" |
| #include "base/macros.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/histogram_tester.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_commands.h" |
| #include "chrome/browser/ui/singleton_tabs.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "content/public/browser/notification_observer.h" |
| #include "content/public/browser/notification_service.h" |
| #include "content/public/browser/notification_types.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/resource_request_details.h" |
| #include "content/public/browser/web_contents_observer.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| |
| // The goal of these tests is to "simulate" exploited renderer processes, which |
| // can send arbitrary IPC messages and confuse browser process internal state, |
| // leading to security bugs. We are trying to verify that the browser doesn't |
| // perform any dangerous operations in such cases. |
| // This is similar to the security_exploit_browsertest.cc tests, but also |
| // includes chrome/ layer concepts such as extensions. |
| class ChromeSecurityExploitBrowserTest : public InProcessBrowserTest { |
| public: |
| ChromeSecurityExploitBrowserTest() {} |
| ~ChromeSecurityExploitBrowserTest() override {} |
| |
| void SetUpOnMainThread() override { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| } |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| // Since we assume exploited renderer process, it can bypass the same origin |
| // policy at will. Simulate that by passing the disable-web-security flag. |
| command_line->AppendSwitch(switches::kDisableWebSecurity); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(ChromeSecurityExploitBrowserTest); |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTest, |
| ChromeExtensionResources) { |
| // Load a page that requests a chrome-extension:// image through XHR. We |
| // expect this load to fail, as it is an illegal request. |
| GURL foo = embedded_test_server()->GetURL("foo.com", |
| "/chrome_extension_resource.html"); |
| |
| content::DOMMessageQueue msg_queue; |
| |
| ui_test_utils::NavigateToURL(browser(), foo); |
| |
| std::string status; |
| std::string expected_status("0"); |
| EXPECT_TRUE(msg_queue.WaitForMessage(&status)); |
| EXPECT_STREQ(status.c_str(), expected_status.c_str()); |
| } |
| |
| // Extension isolation prevents a normal renderer process from being able to |
| // create a "blob:chrome-extension://" resource. |
| IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTest, |
| CreateBlobInExtensionOrigin) { |
| ui_test_utils::NavigateToURL( |
| browser(), |
| embedded_test_server()->GetURL("a.root-servers.net", "/title1.html")); |
| |
| content::RenderFrameHost* rfh = |
| browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame(); |
| |
| // All these are attacker controlled values. The UUID is arbitrary. |
| std::string blob_id = "2ce53a26-0409-45a3-86e5-f8fb9f5566d8"; |
| std::string blob_type = "text/html"; |
| std::string blob_contents = "<script>chrome.extensions</script>"; |
| std::string blob_path = "5881f76e-10d2-410d-8c61-ef210502acfd"; |
| |
| // Target the bookmark manager extension. |
| std::string target_origin = |
| "chrome-extension://eemcgdkfndhakfknompkggombfjjjeno"; |
| |
| // Set up a blob ID and populate it with attacker-controlled value. This |
| // message is allowed, because this data is not in any origin. |
| content::PwnMessageHelper::CreateBlobWithPayload( |
| rfh->GetProcess(), blob_id, blob_type, "", blob_contents); |
| |
| // This IPC should result in a kill because |target_origin| is not commitable |
| // in |rfh->GetProcess()|. |
| base::HistogramTester histograms; |
| content::RenderProcessHostWatcher crash_observer( |
| rfh->GetProcess(), |
| content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); |
| |
| content::PwnMessageHelper::RegisterBlobURL( |
| rfh->GetProcess(), GURL("blob:" + target_origin + "/" + blob_path), |
| blob_id); |
| |
| // If the process is killed, this test passes. |
| crash_observer.Wait(); |
| histograms.ExpectUniqueSample("Stability.BadMessageTerminated.Content", 139, |
| 1); |
| } |
| |
| // Extension isolation prevents a normal renderer process from being able to |
| // create a "filesystem:chrome-extension://sdgkjaghsdg/temporary/" resource. |
| IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTest, |
| CreateFilesystemURLInExtensionOrigin) { |
| GURL page_url = |
| embedded_test_server()->GetURL("a.root-servers.net", "/title1.html"); |
| ui_test_utils::NavigateToURL(browser(), page_url); |
| |
| content::RenderFrameHost* rfh = |
| browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame(); |
| |
| // Block the renderer on operation that never completes, to shield it from |
| // receiving unexpected browser->renderer IPCs that might CHECK. |
| rfh->ExecuteJavaScriptWithUserGestureForTests( |
| base::ASCIIToUTF16("var r = new XMLHttpRequest();" |
| "r.open('GET', '/slow?99999', false);" |
| "r.send(null);" |
| "while (1);")); |
| |
| // JS code that the attacker would like to run in an extension process. |
| std::string payload = "<html><body>pwned.</body></html>"; |
| std::string payload_type = "text/html"; |
| |
| // Target the bookmark manager extension. |
| std::string target_origin = |
| "chrome-extension://eemcgdkfndhakfknompkggombfjjjeno/"; |
| |
| // Set up a blob ID and populate it with the attacker-controlled payload. |
| // This is allowed, because this data is not in any origin; |
| // the UUID is arbitrary. |
| std::string blob_id = "2ce53a26-0409-45a3-86e5-f8fb9f5566d8"; |
| content::PwnMessageHelper::CreateBlobWithPayload(rfh->GetProcess(), blob_id, |
| payload_type, "", payload); |
| |
| // Note: a well-behaved renderer would always send the following message here, |
| // but it's actually not necessary for the original attack to succeed, so we |
| // omit it. As a result there are some log warnings from the quota observer. |
| // |
| // IPC::IpcSecurityTestUtil::PwnMessageReceived( |
| // rfh->GetProcess()->GetChannel(), |
| // FileSystemHostMsg_OpenFileSystem(22, GURL(target_origin), |
| // storage::kFileSystemTypeTemporary)); |
| |
| GURL target_url = |
| GURL("filesystem:" + target_origin + "temporary/exploit.html"); |
| |
| content::PwnMessageHelper::FileSystemCreate(rfh->GetProcess(), 23, target_url, |
| false, false, false); |
| |
| // Write the blob into the file. If successful, this places an |
| // attacker-controlled value in a resource on the extension origin. |
| content::PwnMessageHelper::FileSystemWrite(rfh->GetProcess(), 24, target_url, |
| blob_id, 0); |
| |
| // Now navigate to |target_url| in a new tab. It should not contain |payload|. |
| AddTabAtIndex(0, target_url, ui::PAGE_TRANSITION_TYPED); |
| content::WaitForLoadStop(browser()->tab_strip_model()->GetWebContentsAt(0)); |
| rfh = browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame(); |
| EXPECT_EQ(GURL(target_origin), rfh->GetSiteInstance()->GetSiteURL()); |
| std::string body; |
| std::string script = R"( |
| var textContent = document.body.innerText.replace(/\n+/g, '\n'); |
| window.domAutomationController.send(textContent); |
| )"; |
| |
| EXPECT_TRUE(content::ExecuteScriptAndExtractString(rfh, script, &body)); |
| EXPECT_EQ( |
| "\nYour file was not found\n" |
| "It may have been moved or deleted.\n" |
| "ERR_FILE_NOT_FOUND\n", |
| body); |
| } |