Commit ced2ca85 authored by Lucas Gadani's avatar Lucas Gadani Committed by Commit Bot

CSP: Enforce XFO when CSP doesn't contain frame-ancestors.

Bug: 1040877
Change-Id: I378a501abc21687b34ce017c53ecbd63dbe28e41
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2012806Reviewed-by: default avatarArthur Sonzogni <arthursonzogni@chromium.org>
Commit-Queue: Lucas Gadani <lfg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#734476}
parent 707e7c14
...@@ -193,54 +193,9 @@ NavigationThrottle::ThrottleCheckResult AncestorThrottle::ProcessResponseImpl( ...@@ -193,54 +193,9 @@ NavigationThrottle::ThrottleCheckResult AncestorThrottle::ProcessResponseImpl(
return NavigationThrottle::PROCEED; return NavigationThrottle::PROCEED;
} }
// Evaluate whether the navigation should be allowed or blocked based on
// existing content-security-policy on the response.
if (is_response_check && base::FeatureList::IsEnabled(
network::features::kOutOfBlinkFrameAncestors)) {
if (!request->response()->content_security_policy.empty()) {
// TODO(arthursonzogni): Remove content::ContentSecurityPolicy in favor of
// network::mojom::ContentSecurityPolicy, this will avoid conversion
// between type here.
std::vector<ContentSecurityPolicy> policies;
policies.reserve(request->response()->content_security_policy.size());
for (auto& policy : request->response()->content_security_policy)
policies.push_back(ContentSecurityPolicy(policy.Clone()));
// TODO(lfg): If the initiating document is known and correspond to the
// navigating frame's current document, consider using:
// navigation_request().common_params().source_location here instead.
SourceLocation empty_source_location;
// CSP frame-ancestors are checked against the URL of every parent and are
// reported to the navigating frame.
FrameAncestorCSPContext csp_context(
NavigationRequest::From(navigation_handle())->GetRenderFrameHost(),
policies);
csp_context.SetSelf(url::Origin::Create(navigation_handle()->GetURL()));
// Check CSP frame-ancestors against every parent.
// We enforce frame-ancestors in the outer delegate for portals, but not
// for other uses of inner/outer WebContents (GuestViews).
RenderFrameHostImpl* parent =
ParentForAncestorThrottle(request->GetRenderFrameHost());
while (parent) {
if (!csp_context.IsAllowedByCsp(
network::mojom::CSPDirectiveName::FrameAncestors,
parent->GetLastCommittedOrigin().GetURL(),
navigation_handle()->WasServerRedirect(),
true /* is_response_check */, empty_source_location,
CSPContext::CheckCSPDisposition::CHECK_ALL_CSP,
navigation_handle()->IsFormSubmission())) {
return NavigationThrottle::BLOCK_RESPONSE;
}
parent = ParentForAncestorThrottle(parent);
}
return NavigationThrottle::PROCEED;
}
}
std::string header_value; std::string header_value;
HeaderDisposition disposition = ParseHeader(request->GetResponseHeaders(), HeaderDisposition disposition =
&header_value, is_response_check); ParseHeader(request->GetResponseHeaders(), &header_value);
switch (disposition) { switch (disposition) {
case HeaderDisposition::CONFLICT: case HeaderDisposition::CONFLICT:
...@@ -254,7 +209,7 @@ NavigationThrottle::ThrottleCheckResult AncestorThrottle::ProcessResponseImpl( ...@@ -254,7 +209,7 @@ NavigationThrottle::ThrottleCheckResult AncestorThrottle::ProcessResponseImpl(
ParseError(header_value, disposition); ParseError(header_value, disposition);
RecordXFrameOptionsUsage(XFrameOptionsHistogram::INVALID); RecordXFrameOptionsUsage(XFrameOptionsHistogram::INVALID);
// TODO(mkwst): Consider failing here. // TODO(mkwst): Consider failing here.
return NavigationThrottle::PROCEED; break;
case HeaderDisposition::DENY: case HeaderDisposition::DENY:
if (logging == LoggingDisposition::LOG_TO_CONSOLE) if (logging == LoggingDisposition::LOG_TO_CONSOLE)
...@@ -288,21 +243,64 @@ NavigationThrottle::ThrottleCheckResult AncestorThrottle::ProcessResponseImpl( ...@@ -288,21 +243,64 @@ NavigationThrottle::ThrottleCheckResult AncestorThrottle::ProcessResponseImpl(
parent = parent->parent(); parent = parent->parent();
} }
RecordXFrameOptionsUsage(XFrameOptionsHistogram::SAMEORIGIN); RecordXFrameOptionsUsage(XFrameOptionsHistogram::SAMEORIGIN);
return NavigationThrottle::PROCEED; break;
} }
case HeaderDisposition::NONE: case HeaderDisposition::NONE:
RecordXFrameOptionsUsage(XFrameOptionsHistogram::NONE); RecordXFrameOptionsUsage(XFrameOptionsHistogram::NONE);
return NavigationThrottle::PROCEED; break;
case HeaderDisposition::BYPASS: case HeaderDisposition::BYPASS:
RecordXFrameOptionsUsage(XFrameOptionsHistogram::BYPASS); RecordXFrameOptionsUsage(XFrameOptionsHistogram::BYPASS);
return NavigationThrottle::PROCEED; break;
case HeaderDisposition::ALLOWALL: case HeaderDisposition::ALLOWALL:
RecordXFrameOptionsUsage(XFrameOptionsHistogram::ALLOWALL); RecordXFrameOptionsUsage(XFrameOptionsHistogram::ALLOWALL);
return NavigationThrottle::PROCEED; break;
} }
NOTREACHED();
return NavigationThrottle::BLOCK_RESPONSE; // Evaluate whether the navigation should be allowed or blocked based on
// existing content-security-policy on the response.
if (is_response_check && base::FeatureList::IsEnabled(
network::features::kOutOfBlinkFrameAncestors)) {
// TODO(arthursonzogni): Remove content::ContentSecurityPolicy in favor
// of network::mojom::ContentSecurityPolicy, this will avoid conversion
// between type here.
std::vector<ContentSecurityPolicy> policies;
policies.reserve(request->response()->content_security_policy.size());
for (auto& policy : request->response()->content_security_policy)
policies.push_back(ContentSecurityPolicy(policy.Clone()));
// TODO(lfg): If the initiating document is known and correspond to the
// navigating frame's current document, consider using:
// navigation_request().common_params().source_location here instead.
SourceLocation empty_source_location;
// CSP frame-ancestors are checked against the URL of every parent and
// are reported to the navigating frame.
FrameAncestorCSPContext csp_context(
NavigationRequest::From(navigation_handle())->GetRenderFrameHost(),
policies);
csp_context.SetSelf(url::Origin::Create(navigation_handle()->GetURL()));
// Check CSP frame-ancestors against every parent.
// We enforce frame-ancestors in the outer delegate for portals, but not
// for other uses of inner/outer WebContents (GuestViews).
RenderFrameHostImpl* parent =
ParentForAncestorThrottle(request->GetRenderFrameHost());
while (parent) {
if (!csp_context.IsAllowedByCsp(
network::mojom::CSPDirectiveName::FrameAncestors,
parent->GetLastCommittedOrigin().GetURL(),
navigation_handle()->WasServerRedirect(),
true /* is_response_check */, empty_source_location,
CSPContext::CheckCSPDisposition::CHECK_ALL_CSP,
navigation_handle()->IsFormSubmission())) {
return NavigationThrottle::BLOCK_RESPONSE;
}
parent = ParentForAncestorThrottle(parent);
}
}
return NavigationThrottle::PROCEED;
} }
const char* AncestorThrottle::GetNameForLogging() { const char* AncestorThrottle::GetNameForLogging() {
...@@ -363,8 +361,7 @@ void AncestorThrottle::ConsoleError(HeaderDisposition disposition) { ...@@ -363,8 +361,7 @@ void AncestorThrottle::ConsoleError(HeaderDisposition disposition) {
AncestorThrottle::HeaderDisposition AncestorThrottle::ParseHeader( AncestorThrottle::HeaderDisposition AncestorThrottle::ParseHeader(
const net::HttpResponseHeaders* headers, const net::HttpResponseHeaders* headers,
std::string* header_value, std::string* header_value) {
bool is_response_check) {
DCHECK(header_value); DCHECK(header_value);
if (!headers) if (!headers)
return HeaderDisposition::NONE; return HeaderDisposition::NONE;
...@@ -407,9 +404,6 @@ AncestorThrottle::HeaderDisposition AncestorThrottle::ParseHeader( ...@@ -407,9 +404,6 @@ AncestorThrottle::HeaderDisposition AncestorThrottle::ParseHeader(
if (result != HeaderDisposition::NONE && if (result != HeaderDisposition::NONE &&
result != HeaderDisposition::ALLOWALL && result != HeaderDisposition::ALLOWALL &&
HeadersContainFrameAncestorsCSP(headers)) { HeadersContainFrameAncestorsCSP(headers)) {
DCHECK(!is_response_check ||
!base::FeatureList::IsEnabled(
network::features::kOutOfBlinkFrameAncestors));
// TODO(mkwst): 'frame-ancestors' is currently handled in Blink. We should // TODO(mkwst): 'frame-ancestors' is currently handled in Blink. We should
// handle it here instead. Until then, don't block the request, and let // handle it here instead. Until then, don't block the request, and let
// Blink handle it. https://crbug.com/555418 // Blink handle it. https://crbug.com/555418
......
...@@ -61,8 +61,7 @@ class CONTENT_EXPORT AncestorThrottle : public NavigationThrottle { ...@@ -61,8 +61,7 @@ class CONTENT_EXPORT AncestorThrottle : public NavigationThrottle {
// or INVALID, |header_value| will be populated with the value which caused // or INVALID, |header_value| will be populated with the value which caused
// the parse error. // the parse error.
HeaderDisposition ParseHeader(const net::HttpResponseHeaders* headers, HeaderDisposition ParseHeader(const net::HttpResponseHeaders* headers,
std::string* header_value, std::string* header_value);
bool is_response_check);
DISALLOW_COPY_AND_ASSIGN(AncestorThrottle); DISALLOW_COPY_AND_ASSIGN(AncestorThrottle);
}; };
......
...@@ -89,7 +89,7 @@ TEST_F(AncestorThrottleTest, ParsingXFrameOptions) { ...@@ -89,7 +89,7 @@ TEST_F(AncestorThrottleTest, ParsingXFrameOptions) {
GetAncestorHeaders(test.header, nullptr); GetAncestorHeaders(test.header, nullptr);
std::string header_value; std::string header_value;
EXPECT_EQ(test.expected, EXPECT_EQ(test.expected,
throttle.ParseHeader(headers.get(), &header_value, true)); throttle.ParseHeader(headers.get(), &header_value));
EXPECT_EQ(test.value, header_value); EXPECT_EQ(test.value, header_value);
} }
} }
...@@ -124,7 +124,7 @@ TEST_F(AncestorThrottleTest, ErrorsParsingXFrameOptions) { ...@@ -124,7 +124,7 @@ TEST_F(AncestorThrottleTest, ErrorsParsingXFrameOptions) {
GetAncestorHeaders(test.header, nullptr); GetAncestorHeaders(test.header, nullptr);
std::string header_value; std::string header_value;
EXPECT_EQ(test.expected, EXPECT_EQ(test.expected,
throttle.ParseHeader(headers.get(), &header_value, true)); throttle.ParseHeader(headers.get(), &header_value));
EXPECT_EQ(test.failure, header_value); EXPECT_EQ(test.failure, header_value);
} }
} }
...@@ -184,7 +184,7 @@ TEST_F(AncestorThrottleTest, IgnoreWhenFrameAncestorsPresent) { ...@@ -184,7 +184,7 @@ TEST_F(AncestorThrottleTest, IgnoreWhenFrameAncestorsPresent) {
GetAncestorHeaders("DENY", test.csp); GetAncestorHeaders("DENY", test.csp);
std::string header_value; std::string header_value;
EXPECT_EQ(test.expected, EXPECT_EQ(test.expected,
throttle.ParseHeader(headers.get(), &header_value, true)); throttle.ParseHeader(headers.get(), &header_value));
EXPECT_EQ("DENY", header_value); EXPECT_EQ("DENY", header_value);
} }
} }
......
...@@ -31,4 +31,31 @@ ...@@ -31,4 +31,31 @@
document.body.appendChild(i); document.body.appendChild(i);
}, "`XFO: DENY` blocks cross-origin framing."); }, "`XFO: DENY` blocks cross-origin framing.");
async_test(t => {
var i = document.createElement('iframe');
i.src = "./support/xfo.py?value=DENY&csp_value=default-src%20'self'";
assert_no_message_from(i, t);
i.onload = t.step_func_done(_ => {
assert_equals(i.contentDocument, null);
i.remove();
});
document.body.appendChild(i);
}, "`XFO: DENY` blocks framing when CSP is present without a frame-ancestors directive.");
async_test(t => {
var i = document.createElement('iframe');
i.src = "./support/xfo.py?value=DENY&csp_value=frame-ancestors%20'self'";
wait_for_message_from(i, t)
.then(t.step_func_done(e => {
assert_equals(e.data, "Loaded");
i.remove();
}));
document.body.appendChild(i);
}, "`XFO: DENY` does not blocks framing when CSP is present with a frame-ancestors directive.");
</script> </script>
...@@ -2,7 +2,10 @@ def main(request, response): ...@@ -2,7 +2,10 @@ def main(request, response):
headers = [("Content-Type", "text/html"), ("X-Frame-Options", request.GET.first("value"))] headers = [("Content-Type", "text/html"), ("X-Frame-Options", request.GET.first("value"))]
if "value2" in request.GET: if "value2" in request.GET:
headers.append(("X-Frame-Options", request.GET.first("value2"))) headers.append(("X-Frame-Options", request.GET.first("value2")))
if "csp_value" in request.GET:
headers.append(("Content-Security-Policy", request.GET.first("csp_value")))
body = """<!DOCTYPE html> body = """<!DOCTYPE html>
<html> <html>
...@@ -16,5 +19,3 @@ def main(request, response): ...@@ -16,5 +19,3 @@ def main(request, response):
</html> </html>
""" """
return (headers, body) return (headers, body)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment