blob: d5cf578094757ed67d60c028d75e1f5e91ab66f9 [file] [log] [blame]
// Copyright 2017 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 <set>
#include "content/common/content_security_policy/csp_context.h"
#include "content/common/content_security_policy_header.h"
#include "content/common/navigation_params.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
namespace {
class CSPContextTest : public CSPContext {
public:
const std::vector<CSPViolationParams>& violations() { return violations_; }
void AddSchemeToBypassCSP(const std::string& scheme) {
scheme_to_bypass_.insert(scheme);
}
bool SchemeShouldBypassCSP(const base::StringPiece& scheme) override {
return scheme_to_bypass_.count(scheme.as_string());
}
void ClearViolations() { violations_.clear(); }
void set_sanitize_data_for_use_in_csp_violation(bool value) {
sanitize_data_for_use_in_csp_violation_ = value;
}
void SanitizeDataForUseInCspViolation(
bool is_redirect,
CSPDirective::Name directive,
GURL* blocked_url,
SourceLocation* source_location) const override {
if (!sanitize_data_for_use_in_csp_violation_)
return;
*blocked_url = blocked_url->GetOrigin();
*source_location =
SourceLocation(GURL(source_location->url).GetOrigin().spec(), 0u, 0u);
}
private:
void ReportContentSecurityPolicyViolation(
const CSPViolationParams& violation_params) override {
violations_.push_back(violation_params);
}
std::vector<CSPViolationParams> violations_;
std::set<std::string> scheme_to_bypass_;
bool sanitize_data_for_use_in_csp_violation_ = false;
};
// Build a new policy made of only one directive and no report endpoints.
ContentSecurityPolicy BuildPolicy(CSPDirective::Name directive_name,
std::vector<CSPSource> sources) {
return ContentSecurityPolicy(
ContentSecurityPolicyHeader(std::string(), // header
blink::kWebContentSecurityPolicyTypeEnforce,
blink::kWebContentSecurityPolicySourceHTTP),
{CSPDirective(directive_name, CSPSourceList(false, false, sources))},
std::vector<std::string>(), false); // report_end_points
}
} // namespace
TEST(CSPContextTest, SchemeShouldBypassCSP) {
CSPSource source("", "example.com", false, url::PORT_UNSPECIFIED, false, "");
CSPContextTest context;
context.AddContentSecurityPolicy(
BuildPolicy(CSPDirective::DefaultSrc, {source}));
EXPECT_FALSE(context.IsAllowedByCsp(
CSPDirective::FrameSrc, GURL("data:text/html,<html></html>"), false,
SourceLocation(), CSPContext::CHECK_ALL_CSP));
context.AddSchemeToBypassCSP("data");
EXPECT_TRUE(context.IsAllowedByCsp(
CSPDirective::FrameSrc, GURL("data:text/html,<html></html>"), false,
SourceLocation(), CSPContext::CHECK_ALL_CSP));
}
TEST(CSPContextTest, MultiplePolicies) {
CSPContextTest context;
context.SetSelf(url::Origin::Create(GURL("http://example.com")));
CSPSource source_a("", "a.com", false, url::PORT_UNSPECIFIED, false, "");
CSPSource source_b("", "b.com", false, url::PORT_UNSPECIFIED, false, "");
CSPSource source_c("", "c.com", false, url::PORT_UNSPECIFIED, false, "");
context.AddContentSecurityPolicy(
BuildPolicy(CSPDirective::FrameSrc, {source_a, source_b}));
context.AddContentSecurityPolicy(
BuildPolicy(CSPDirective::FrameSrc, {source_a, source_c}));
EXPECT_TRUE(context.IsAllowedByCsp(
CSPDirective::FrameSrc, GURL("http://a.com"), false, SourceLocation(),
CSPContext::CHECK_ALL_CSP));
EXPECT_FALSE(context.IsAllowedByCsp(
CSPDirective::FrameSrc, GURL("http://b.com"), false, SourceLocation(),
CSPContext::CHECK_ALL_CSP));
EXPECT_FALSE(context.IsAllowedByCsp(
CSPDirective::FrameSrc, GURL("http://c.com"), false, SourceLocation(),
CSPContext::CHECK_ALL_CSP));
EXPECT_FALSE(context.IsAllowedByCsp(
CSPDirective::FrameSrc, GURL("http://d.com"), false, SourceLocation(),
CSPContext::CHECK_ALL_CSP));
}
TEST(CSPContextTest, SanitizeDataForUseInCspViolation) {
CSPContextTest context;
context.SetSelf(url::Origin::Create(GURL("http://a.com")));
// Content-Security-Policy: frame-src "a.com/iframe"
context.AddContentSecurityPolicy(
BuildPolicy(CSPDirective::FrameSrc,
{CSPSource("", "a.com", false, url::PORT_UNSPECIFIED, false,
"/iframe")}));
GURL blocked_url("http://a.com/login?password=1234");
SourceLocation source_location("http://a.com/login", 10u, 20u);
// When the |blocked_url| and |source_location| aren't sensitive information.
{
EXPECT_FALSE(context.IsAllowedByCsp(CSPDirective::FrameSrc, blocked_url,
false, source_location,
CSPContext::CHECK_ALL_CSP));
ASSERT_EQ(1u, context.violations().size());
EXPECT_EQ(context.violations()[0].blocked_url, blocked_url);
EXPECT_EQ(context.violations()[0].source_location.url,
"http://a.com/login");
EXPECT_EQ(context.violations()[0].source_location.line_number, 10u);
EXPECT_EQ(context.violations()[0].source_location.column_number, 20u);
EXPECT_EQ(context.violations()[0].console_message,
"Refused to frame 'http://a.com/login?password=1234' because it "
"violates the following Content Security Policy directive: "
"\"frame-src a.com/iframe\".\n");
}
context.set_sanitize_data_for_use_in_csp_violation(true);
// When the |blocked_url| and |source_location| are sensitive information.
{
EXPECT_FALSE(context.IsAllowedByCsp(CSPDirective::FrameSrc, blocked_url,
false, source_location,
CSPContext::CHECK_ALL_CSP));
ASSERT_EQ(2u, context.violations().size());
EXPECT_EQ(context.violations()[1].blocked_url, blocked_url.GetOrigin());
EXPECT_EQ(context.violations()[1].source_location.url, "http://a.com/");
EXPECT_EQ(context.violations()[1].source_location.line_number, 0u);
EXPECT_EQ(context.violations()[1].source_location.column_number, 0u);
EXPECT_EQ(context.violations()[1].console_message,
"Refused to frame 'http://a.com/' because it violates the "
"following Content Security Policy directive: \"frame-src "
"a.com/iframe\".\n");
}
}
// When several policies are infringed, all of them must be reported.
TEST(CSPContextTest, MultipleInfringement) {
CSPContextTest context;
context.SetSelf(url::Origin::Create(GURL("http://example.com")));
CSPSource source_a("", "a.com", false, url::PORT_UNSPECIFIED, false, "");
CSPSource source_b("", "b.com", false, url::PORT_UNSPECIFIED, false, "");
CSPSource source_c("", "c.com", false, url::PORT_UNSPECIFIED, false, "");
context.AddContentSecurityPolicy(
BuildPolicy(CSPDirective::FrameSrc, {source_a}));
context.AddContentSecurityPolicy(
BuildPolicy(CSPDirective::FrameSrc, {source_b}));
context.AddContentSecurityPolicy(
BuildPolicy(CSPDirective::FrameSrc, {source_c}));
EXPECT_FALSE(context.IsAllowedByCsp(
CSPDirective::FrameSrc, GURL("http://c.com"), false, SourceLocation(),
CSPContext::CHECK_ALL_CSP));
ASSERT_EQ(2u, context.violations().size());
const char console_message_a[] =
"Refused to frame 'http://c.com/' because it violates the following "
"Content Security Policy directive: \"frame-src a.com\".\n";
const char console_message_b[] =
"Refused to frame 'http://c.com/' because it violates the following "
"Content Security Policy directive: \"frame-src b.com\".\n";
EXPECT_EQ(console_message_a, context.violations()[0].console_message);
EXPECT_EQ(console_message_b, context.violations()[1].console_message);
}
// Tests that the CheckCSPDisposition parameter is obeyed.
TEST(CSPContextTest, CheckCSPDisposition) {
CSPSource source("", "example.com", false, url::PORT_UNSPECIFIED, false, "");
CSPContextTest context;
// Add an enforced policy.
context.AddContentSecurityPolicy(
BuildPolicy(CSPDirective::FrameSrc, {source}));
// Add a report-only policy.
ContentSecurityPolicy report_only =
BuildPolicy(CSPDirective::DefaultSrc, {source});
report_only.header.type = blink::kWebContentSecurityPolicyTypeReport;
context.AddContentSecurityPolicy(report_only);
// With CHECK_ALL_CSP, both policies should be checked and violations should
// be reported.
EXPECT_FALSE(context.IsAllowedByCsp(
CSPDirective::FrameSrc, GURL("https://not-example.com"), false,
SourceLocation(), CSPContext::CHECK_ALL_CSP));
ASSERT_EQ(2u, context.violations().size());
const char console_message_a[] =
"Refused to frame 'https://not-example.com/' because it violates the "
"following "
"Content Security Policy directive: \"frame-src example.com\".\n";
const char console_message_b[] =
"[Report Only] Refused to frame 'https://not-example.com/' because it "
"violates the following "
"Content Security Policy directive: \"default-src example.com\". Note "
"that 'frame-src' was not explicitly set, so 'default-src' is used as a "
"fallback.\n";
// Both console messages must appear in the reported violations.
EXPECT_TRUE((console_message_a == context.violations()[0].console_message &&
console_message_b == context.violations()[1].console_message) ||
(console_message_a == context.violations()[1].console_message &&
console_message_b == context.violations()[0].console_message));
// With CHECK_REPORT_ONLY_CSP, the request should be allowed but reported.
context.ClearViolations();
EXPECT_TRUE(context.IsAllowedByCsp(
CSPDirective::FrameSrc, GURL("https://not-example.com"), false,
SourceLocation(), CSPContext::CHECK_REPORT_ONLY_CSP));
ASSERT_EQ(1u, context.violations().size());
EXPECT_EQ(console_message_b, context.violations()[0].console_message);
// With CHECK_ENFORCED_CSP, the request should be blocked and only the
// enforced policy violation should be reported.
context.ClearViolations();
EXPECT_FALSE(context.IsAllowedByCsp(
CSPDirective::FrameSrc, GURL("https://not-example.com"), false,
SourceLocation(), CSPContext::CHECK_ENFORCED_CSP));
ASSERT_EQ(1u, context.violations().size());
EXPECT_EQ(console_message_a, context.violations()[0].console_message);
}
// Tests HTTP subresources and form submissions have their URLs upgraded when
// upgrade-insecure-requests is present.
TEST(CSPContextTest, ShouldModifyRequestUrlForCsp) {
CSPContextTest context;
context.AddContentSecurityPolicy(BuildPolicy(
CSPDirective::UpgradeInsecureRequests, std::vector<CSPSource>()));
GURL new_url;
// An HTTP subresource or form submission should be upgraded.
EXPECT_TRUE(context.ShouldModifyRequestUrlForCsp(GURL("http://example.com"),
true, &new_url));
EXPECT_EQ(GURL("https://example.com"), new_url);
EXPECT_TRUE(context.ShouldModifyRequestUrlForCsp(
GURL("http://example.com:80"), true, &new_url));
EXPECT_EQ(GURL("https://example.com:443"), new_url);
// Non-standard ports should not be modified.
EXPECT_TRUE(context.ShouldModifyRequestUrlForCsp(
GURL("http://example-weird-port.com:8088"), true, &new_url));
EXPECT_EQ(GURL("https://example-weird-port.com:8088"), new_url);
// Non-HTTP URLs don't need to be modified.
EXPECT_FALSE(context.ShouldModifyRequestUrlForCsp(GURL("https://example.com"),
true, &new_url));
EXPECT_FALSE(context.ShouldModifyRequestUrlForCsp(
GURL("data:text/html,<html></html>"), true, &new_url));
// Nor do main-frame navigation requests.
EXPECT_FALSE(context.ShouldModifyRequestUrlForCsp(GURL("http://example.com"),
false, &new_url));
}
} // namespace content