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 |