| OLD | NEW |
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. | 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include <set> | 5 #include <set> |
| 6 | 6 |
| 7 #include "content/common/content_security_policy/csp_context.h" | 7 #include "content/common/content_security_policy/csp_context.h" |
| 8 #include "content/common/content_security_policy_header.h" | 8 #include "content/common/content_security_policy_header.h" |
| 9 #include "content/common/navigation_params.h" | 9 #include "content/common/navigation_params.h" |
| 10 #include "testing/gtest/include/gtest/gtest.h" | 10 #include "testing/gtest/include/gtest/gtest.h" |
| 11 | 11 |
| 12 namespace content { | 12 namespace content { |
| 13 | 13 |
| 14 namespace { | 14 namespace { |
| 15 | 15 |
| 16 class CSPContextTest : public CSPContext { | 16 class CSPContextTest : public CSPContext { |
| 17 public: | 17 public: |
| 18 const std::vector<CSPViolationParams>& violations() { return violations_; } | 18 const std::vector<CSPViolationParams>& violations() { return violations_; } |
| 19 | 19 |
| 20 void AddSchemeToBypassCSP(const std::string& scheme) { | 20 void AddSchemeToBypassCSP(const std::string& scheme) { |
| 21 scheme_to_bypass_.insert(scheme); | 21 scheme_to_bypass_.insert(scheme); |
| 22 } | 22 } |
| 23 | 23 |
| 24 bool SchemeShouldBypassCSP(const base::StringPiece& scheme) override { | 24 bool SchemeShouldBypassCSP(const base::StringPiece& scheme) override { |
| 25 return scheme_to_bypass_.count(scheme.as_string()); | 25 return scheme_to_bypass_.count(scheme.as_string()); |
| 26 } | 26 } |
| 27 | 27 |
| 28 void ClearViolations() { violations_.clear(); } |
| 29 |
| 28 void set_sanitize_data_for_use_in_csp_violation(bool value) { | 30 void set_sanitize_data_for_use_in_csp_violation(bool value) { |
| 29 sanitize_data_for_use_in_csp_violation_ = value; | 31 sanitize_data_for_use_in_csp_violation_ = value; |
| 30 } | 32 } |
| 31 | 33 |
| 32 void SanitizeDataForUseInCspViolation( | 34 void SanitizeDataForUseInCspViolation( |
| 33 bool is_redirect, | 35 bool is_redirect, |
| 34 CSPDirective::Name directive, | 36 CSPDirective::Name directive, |
| 35 GURL* blocked_url, | 37 GURL* blocked_url, |
| 36 SourceLocation* source_location) const override { | 38 SourceLocation* source_location) const override { |
| 37 if (!sanitize_data_for_use_in_csp_violation_) | 39 if (!sanitize_data_for_use_in_csp_violation_) |
| (...skipping 25 matching lines...) Expand all Loading... |
| 63 } | 65 } |
| 64 | 66 |
| 65 } // namespace | 67 } // namespace |
| 66 | 68 |
| 67 TEST(CSPContextTest, SchemeShouldBypassCSP) { | 69 TEST(CSPContextTest, SchemeShouldBypassCSP) { |
| 68 CSPSource source("", "example.com", false, url::PORT_UNSPECIFIED, false, ""); | 70 CSPSource source("", "example.com", false, url::PORT_UNSPECIFIED, false, ""); |
| 69 CSPContextTest context; | 71 CSPContextTest context; |
| 70 context.AddContentSecurityPolicy( | 72 context.AddContentSecurityPolicy( |
| 71 BuildPolicy(CSPDirective::DefaultSrc, {source})); | 73 BuildPolicy(CSPDirective::DefaultSrc, {source})); |
| 72 | 74 |
| 73 EXPECT_FALSE(context.IsAllowedByCsp(CSPDirective::FrameSrc, | 75 EXPECT_FALSE(context.IsAllowedByCsp( |
| 74 GURL("data:text/html,<html></html>"), | 76 CSPDirective::FrameSrc, GURL("data:text/html,<html></html>"), false, |
| 75 false, SourceLocation())); | 77 SourceLocation(), CSPContext::CHECK_ALL_CSP)); |
| 76 | 78 |
| 77 context.AddSchemeToBypassCSP("data"); | 79 context.AddSchemeToBypassCSP("data"); |
| 78 | 80 |
| 79 EXPECT_TRUE(context.IsAllowedByCsp(CSPDirective::FrameSrc, | 81 EXPECT_TRUE(context.IsAllowedByCsp( |
| 80 GURL("data:text/html,<html></html>"), | 82 CSPDirective::FrameSrc, GURL("data:text/html,<html></html>"), false, |
| 81 false, SourceLocation())); | 83 SourceLocation(), CSPContext::CHECK_ALL_CSP)); |
| 82 } | 84 } |
| 83 | 85 |
| 84 TEST(CSPContextTest, MultiplePolicies) { | 86 TEST(CSPContextTest, MultiplePolicies) { |
| 85 CSPContextTest context; | 87 CSPContextTest context; |
| 86 context.SetSelf(url::Origin(GURL("http://example.com"))); | 88 context.SetSelf(url::Origin(GURL("http://example.com"))); |
| 87 | 89 |
| 88 CSPSource source_a("", "a.com", false, url::PORT_UNSPECIFIED, false, ""); | 90 CSPSource source_a("", "a.com", false, url::PORT_UNSPECIFIED, false, ""); |
| 89 CSPSource source_b("", "b.com", false, url::PORT_UNSPECIFIED, false, ""); | 91 CSPSource source_b("", "b.com", false, url::PORT_UNSPECIFIED, false, ""); |
| 90 CSPSource source_c("", "c.com", false, url::PORT_UNSPECIFIED, false, ""); | 92 CSPSource source_c("", "c.com", false, url::PORT_UNSPECIFIED, false, ""); |
| 91 | 93 |
| 92 context.AddContentSecurityPolicy( | 94 context.AddContentSecurityPolicy( |
| 93 BuildPolicy(CSPDirective::FrameSrc, {source_a, source_b})); | 95 BuildPolicy(CSPDirective::FrameSrc, {source_a, source_b})); |
| 94 context.AddContentSecurityPolicy( | 96 context.AddContentSecurityPolicy( |
| 95 BuildPolicy(CSPDirective::FrameSrc, {source_a, source_c})); | 97 BuildPolicy(CSPDirective::FrameSrc, {source_a, source_c})); |
| 96 | 98 |
| 97 EXPECT_TRUE(context.IsAllowedByCsp( | 99 EXPECT_TRUE(context.IsAllowedByCsp( |
| 98 CSPDirective::FrameSrc, GURL("http://a.com"), false, SourceLocation())); | 100 CSPDirective::FrameSrc, GURL("http://a.com"), false, SourceLocation(), |
| 101 CSPContext::CHECK_ALL_CSP)); |
| 99 EXPECT_FALSE(context.IsAllowedByCsp( | 102 EXPECT_FALSE(context.IsAllowedByCsp( |
| 100 CSPDirective::FrameSrc, GURL("http://b.com"), false, SourceLocation())); | 103 CSPDirective::FrameSrc, GURL("http://b.com"), false, SourceLocation(), |
| 104 CSPContext::CHECK_ALL_CSP)); |
| 101 EXPECT_FALSE(context.IsAllowedByCsp( | 105 EXPECT_FALSE(context.IsAllowedByCsp( |
| 102 CSPDirective::FrameSrc, GURL("http://c.com"), false, SourceLocation())); | 106 CSPDirective::FrameSrc, GURL("http://c.com"), false, SourceLocation(), |
| 107 CSPContext::CHECK_ALL_CSP)); |
| 103 EXPECT_FALSE(context.IsAllowedByCsp( | 108 EXPECT_FALSE(context.IsAllowedByCsp( |
| 104 CSPDirective::FrameSrc, GURL("http://d.com"), false, SourceLocation())); | 109 CSPDirective::FrameSrc, GURL("http://d.com"), false, SourceLocation(), |
| 110 CSPContext::CHECK_ALL_CSP)); |
| 105 } | 111 } |
| 106 | 112 |
| 107 TEST(CSPContextTest, SanitizeDataForUseInCspViolation) { | 113 TEST(CSPContextTest, SanitizeDataForUseInCspViolation) { |
| 108 CSPContextTest context; | 114 CSPContextTest context; |
| 109 context.SetSelf(url::Origin(GURL("http://a.com"))); | 115 context.SetSelf(url::Origin(GURL("http://a.com"))); |
| 110 | 116 |
| 111 // Content-Security-Policy: frame-src "a.com/iframe" | 117 // Content-Security-Policy: frame-src "a.com/iframe" |
| 112 context.AddContentSecurityPolicy( | 118 context.AddContentSecurityPolicy( |
| 113 BuildPolicy(CSPDirective::FrameSrc, | 119 BuildPolicy(CSPDirective::FrameSrc, |
| 114 {CSPSource("", "a.com", false, url::PORT_UNSPECIFIED, false, | 120 {CSPSource("", "a.com", false, url::PORT_UNSPECIFIED, false, |
| 115 "/iframe")})); | 121 "/iframe")})); |
| 116 | 122 |
| 117 GURL blocked_url("http://a.com/login?password=1234"); | 123 GURL blocked_url("http://a.com/login?password=1234"); |
| 118 SourceLocation source_location("http://a.com/login", 10u, 20u); | 124 SourceLocation source_location("http://a.com/login", 10u, 20u); |
| 119 | 125 |
| 120 // When the |blocked_url| and |source_location| aren't sensitive information. | 126 // When the |blocked_url| and |source_location| aren't sensitive information. |
| 121 { | 127 { |
| 122 EXPECT_FALSE(context.IsAllowedByCsp(CSPDirective::FrameSrc, blocked_url, | 128 EXPECT_FALSE(context.IsAllowedByCsp(CSPDirective::FrameSrc, blocked_url, |
| 123 false, source_location)); | 129 false, source_location, |
| 130 CSPContext::CHECK_ALL_CSP)); |
| 124 ASSERT_EQ(1u, context.violations().size()); | 131 ASSERT_EQ(1u, context.violations().size()); |
| 125 EXPECT_EQ(context.violations()[0].blocked_url, blocked_url); | 132 EXPECT_EQ(context.violations()[0].blocked_url, blocked_url); |
| 126 EXPECT_EQ(context.violations()[0].source_location.url, | 133 EXPECT_EQ(context.violations()[0].source_location.url, |
| 127 "http://a.com/login"); | 134 "http://a.com/login"); |
| 128 EXPECT_EQ(context.violations()[0].source_location.line_number, 10u); | 135 EXPECT_EQ(context.violations()[0].source_location.line_number, 10u); |
| 129 EXPECT_EQ(context.violations()[0].source_location.column_number, 20u); | 136 EXPECT_EQ(context.violations()[0].source_location.column_number, 20u); |
| 130 EXPECT_EQ(context.violations()[0].console_message, | 137 EXPECT_EQ(context.violations()[0].console_message, |
| 131 "Refused to frame 'http://a.com/login?password=1234' because it " | 138 "Refused to frame 'http://a.com/login?password=1234' because it " |
| 132 "violates the following Content Security Policy directive: " | 139 "violates the following Content Security Policy directive: " |
| 133 "\"frame-src a.com/iframe\".\n"); | 140 "\"frame-src a.com/iframe\".\n"); |
| 134 } | 141 } |
| 135 | 142 |
| 136 context.set_sanitize_data_for_use_in_csp_violation(true); | 143 context.set_sanitize_data_for_use_in_csp_violation(true); |
| 137 | 144 |
| 138 // When the |blocked_url| and |source_location| are sensitive information. | 145 // When the |blocked_url| and |source_location| are sensitive information. |
| 139 { | 146 { |
| 140 EXPECT_FALSE(context.IsAllowedByCsp(CSPDirective::FrameSrc, blocked_url, | 147 EXPECT_FALSE(context.IsAllowedByCsp(CSPDirective::FrameSrc, blocked_url, |
| 141 false, source_location)); | 148 false, source_location, |
| 149 CSPContext::CHECK_ALL_CSP)); |
| 142 ASSERT_EQ(2u, context.violations().size()); | 150 ASSERT_EQ(2u, context.violations().size()); |
| 143 EXPECT_EQ(context.violations()[1].blocked_url, blocked_url.GetOrigin()); | 151 EXPECT_EQ(context.violations()[1].blocked_url, blocked_url.GetOrigin()); |
| 144 EXPECT_EQ(context.violations()[1].source_location.url, "http://a.com/"); | 152 EXPECT_EQ(context.violations()[1].source_location.url, "http://a.com/"); |
| 145 EXPECT_EQ(context.violations()[1].source_location.line_number, 0u); | 153 EXPECT_EQ(context.violations()[1].source_location.line_number, 0u); |
| 146 EXPECT_EQ(context.violations()[1].source_location.column_number, 0u); | 154 EXPECT_EQ(context.violations()[1].source_location.column_number, 0u); |
| 147 EXPECT_EQ(context.violations()[1].console_message, | 155 EXPECT_EQ(context.violations()[1].console_message, |
| 148 "Refused to frame 'http://a.com/' because it violates the " | 156 "Refused to frame 'http://a.com/' because it violates the " |
| 149 "following Content Security Policy directive: \"frame-src " | 157 "following Content Security Policy directive: \"frame-src " |
| 150 "a.com/iframe\".\n"); | 158 "a.com/iframe\".\n"); |
| 151 } | 159 } |
| 152 } | 160 } |
| 153 | 161 |
| 154 // When several policies are infringed, all of them must be reported. | 162 // When several policies are infringed, all of them must be reported. |
| 155 TEST(CSPContextTest, MultipleInfringement) { | 163 TEST(CSPContextTest, MultipleInfringement) { |
| 156 CSPContextTest context; | 164 CSPContextTest context; |
| 157 context.SetSelf(url::Origin(GURL("http://example.com"))); | 165 context.SetSelf(url::Origin(GURL("http://example.com"))); |
| 158 | 166 |
| 159 CSPSource source_a("", "a.com", false, url::PORT_UNSPECIFIED, false, ""); | 167 CSPSource source_a("", "a.com", false, url::PORT_UNSPECIFIED, false, ""); |
| 160 CSPSource source_b("", "b.com", false, url::PORT_UNSPECIFIED, false, ""); | 168 CSPSource source_b("", "b.com", false, url::PORT_UNSPECIFIED, false, ""); |
| 161 CSPSource source_c("", "c.com", false, url::PORT_UNSPECIFIED, false, ""); | 169 CSPSource source_c("", "c.com", false, url::PORT_UNSPECIFIED, false, ""); |
| 162 | 170 |
| 163 context.AddContentSecurityPolicy( | 171 context.AddContentSecurityPolicy( |
| 164 BuildPolicy(CSPDirective::FrameSrc, {source_a})); | 172 BuildPolicy(CSPDirective::FrameSrc, {source_a})); |
| 165 context.AddContentSecurityPolicy( | 173 context.AddContentSecurityPolicy( |
| 166 BuildPolicy(CSPDirective::FrameSrc, {source_b})); | 174 BuildPolicy(CSPDirective::FrameSrc, {source_b})); |
| 167 context.AddContentSecurityPolicy( | 175 context.AddContentSecurityPolicy( |
| 168 BuildPolicy(CSPDirective::FrameSrc, {source_c})); | 176 BuildPolicy(CSPDirective::FrameSrc, {source_c})); |
| 169 | 177 |
| 170 EXPECT_FALSE(context.IsAllowedByCsp( | 178 EXPECT_FALSE(context.IsAllowedByCsp( |
| 171 CSPDirective::FrameSrc, GURL("http://c.com"), false, SourceLocation())); | 179 CSPDirective::FrameSrc, GURL("http://c.com"), false, SourceLocation(), |
| 180 CSPContext::CHECK_ALL_CSP)); |
| 172 ASSERT_EQ(2u, context.violations().size()); | 181 ASSERT_EQ(2u, context.violations().size()); |
| 173 const char console_message_a[] = | 182 const char console_message_a[] = |
| 174 "Refused to frame 'http://c.com/' because it violates the following " | 183 "Refused to frame 'http://c.com/' because it violates the following " |
| 175 "Content Security Policy directive: \"frame-src a.com\".\n"; | 184 "Content Security Policy directive: \"frame-src a.com\".\n"; |
| 176 const char console_message_b[] = | 185 const char console_message_b[] = |
| 177 "Refused to frame 'http://c.com/' because it violates the following " | 186 "Refused to frame 'http://c.com/' because it violates the following " |
| 178 "Content Security Policy directive: \"frame-src b.com\".\n"; | 187 "Content Security Policy directive: \"frame-src b.com\".\n"; |
| 179 EXPECT_EQ(console_message_a, context.violations()[0].console_message); | 188 EXPECT_EQ(console_message_a, context.violations()[0].console_message); |
| 180 EXPECT_EQ(console_message_b, context.violations()[1].console_message); | 189 EXPECT_EQ(console_message_b, context.violations()[1].console_message); |
| 181 } | 190 } |
| 182 | 191 |
| 192 // Tests that the CheckCSPDisposition parameter is obeyed. |
| 193 TEST(CSPContextTest, CheckCSPDisposition) { |
| 194 CSPSource source("", "example.com", false, url::PORT_UNSPECIFIED, false, ""); |
| 195 CSPContextTest context; |
| 196 |
| 197 // Add an enforced policy. |
| 198 context.AddContentSecurityPolicy( |
| 199 BuildPolicy(CSPDirective::FrameSrc, {source})); |
| 200 |
| 201 // Add a report-only policy. |
| 202 ContentSecurityPolicy report_only = |
| 203 BuildPolicy(CSPDirective::DefaultSrc, {source}); |
| 204 report_only.header.type = blink::kWebContentSecurityPolicyTypeReport; |
| 205 context.AddContentSecurityPolicy(report_only); |
| 206 |
| 207 // With CHECK_ALL_CSP, both policies should be checked and violations should |
| 208 // be reported. |
| 209 EXPECT_FALSE(context.IsAllowedByCsp( |
| 210 CSPDirective::FrameSrc, GURL("https://not-example.com"), false, |
| 211 SourceLocation(), CSPContext::CHECK_ALL_CSP)); |
| 212 ASSERT_EQ(2u, context.violations().size()); |
| 213 const char console_message_a[] = |
| 214 "Refused to frame 'https://not-example.com/' because it violates the " |
| 215 "following " |
| 216 "Content Security Policy directive: \"frame-src example.com\".\n"; |
| 217 const char console_message_b[] = |
| 218 "[Report Only] Refused to frame 'https://not-example.com/' because it " |
| 219 "violates the following " |
| 220 "Content Security Policy directive: \"default-src example.com\". Note " |
| 221 "that 'frame-src' was not explicitly set, so 'default-src' is used as a " |
| 222 "fallback.\n"; |
| 223 // Both console messages must appear in the reported violations. |
| 224 EXPECT_TRUE((console_message_a == context.violations()[0].console_message && |
| 225 console_message_b == context.violations()[1].console_message) || |
| 226 (console_message_a == context.violations()[1].console_message && |
| 227 console_message_b == context.violations()[0].console_message)); |
| 228 |
| 229 // With CHECK_REPORT_ONLY_CSP, the request should be allowed but reported. |
| 230 context.ClearViolations(); |
| 231 EXPECT_TRUE(context.IsAllowedByCsp( |
| 232 CSPDirective::FrameSrc, GURL("https://not-example.com"), false, |
| 233 SourceLocation(), CSPContext::CHECK_REPORT_ONLY_CSP)); |
| 234 ASSERT_EQ(1u, context.violations().size()); |
| 235 EXPECT_EQ(console_message_b, context.violations()[0].console_message); |
| 236 |
| 237 // With CHECK_ENFORCED_CSP, the request should be blocked and only the |
| 238 // enforced policy violation should be reported. |
| 239 context.ClearViolations(); |
| 240 EXPECT_FALSE(context.IsAllowedByCsp( |
| 241 CSPDirective::FrameSrc, GURL("https://not-example.com"), false, |
| 242 SourceLocation(), CSPContext::CHECK_ENFORCED_CSP)); |
| 243 ASSERT_EQ(1u, context.violations().size()); |
| 244 EXPECT_EQ(console_message_a, context.violations()[0].console_message); |
| 245 } |
| 246 |
| 247 // Tests HTTP subresources and form submissions have their URLs upgraded when |
| 248 // upgrade-insecure-requests is present. |
| 249 TEST(CSPContextTest, ShouldModifyRequestUrlForCsp) { |
| 250 CSPContextTest context; |
| 251 context.AddContentSecurityPolicy(BuildPolicy( |
| 252 CSPDirective::UpgradeInsecureRequests, std::vector<CSPSource>())); |
| 253 GURL new_url; |
| 254 // An HTTP subresource or form submission should be upgraded. |
| 255 EXPECT_TRUE(context.ShouldModifyRequestUrlForCsp(GURL("http://example.com"), |
| 256 true, &new_url)); |
| 257 EXPECT_EQ(GURL("https://example.com"), new_url); |
| 258 EXPECT_TRUE(context.ShouldModifyRequestUrlForCsp( |
| 259 GURL("http://example.com:80"), true, &new_url)); |
| 260 EXPECT_EQ(GURL("https://example.com:443"), new_url); |
| 261 // Non-standard ports should not be modified. |
| 262 EXPECT_TRUE(context.ShouldModifyRequestUrlForCsp( |
| 263 GURL("http://example-weird-port.com:8088"), true, &new_url)); |
| 264 EXPECT_EQ(GURL("https://example-weird-port.com:8088"), new_url); |
| 265 |
| 266 // Non-HTTP URLs don't need to be modified. |
| 267 EXPECT_FALSE(context.ShouldModifyRequestUrlForCsp(GURL("https://example.com"), |
| 268 true, &new_url)); |
| 269 EXPECT_FALSE(context.ShouldModifyRequestUrlForCsp( |
| 270 GURL("data:text/html,<html></html>"), true, &new_url)); |
| 271 // Nor do main-frame navigation requests. |
| 272 EXPECT_FALSE(context.ShouldModifyRequestUrlForCsp(GURL("http://example.com"), |
| 273 false, &new_url)); |
| 274 } |
| 275 |
| 183 } // namespace content | 276 } // namespace content |
| OLD | NEW |