Commit befd0655 authored by mkwst@chromium.org's avatar mkwst@chromium.org

CSP: 'frame-ancestors' should override 'x-frame-options'.

As specified in [1], the 'frame-ancestors' CSP directive should take
control of the access checks when loading a document. In particular,
the 'x-frame-options' header should be ignored if a 'frame-ancestors'
directive is present and enforced.

[1]: https://w3c.github.io/webappsec/specs/content-security-policy/#frame-ancestors-and-frame-options

BUG=510423
R=estark@chromium.org

Review URL: https://codereview.chromium.org/1326823003

git-svn-id: svn://svn.chromium.org/blink/trunk@201959 bbb929c8-8fbe-4397-9dbb-9b2b20218538
parent 6986f59c
<!DOCTYPE html>
<html>
<head>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<script>
async_test(function (t) {
var i = document.createElement('iframe');
i.src = "../../resources/frame-ancestors-and-x-frame-options.pl?policy='self'&xfo=DENY";
i.onload = t.step_func_done(function () {
assert_equals(i.contentDocument.origin, document.origin, "The same-origin page loaded.");
});
document.body.appendChild(i);
}, "A 'frame-ancestors' CSP directive overrides an 'x-frame-options' header which would block the page.");
async_test(function (t) {
var i = document.createElement('iframe');
i.src = "../../resources/frame-ancestors-and-x-frame-options.pl?policy=other-origin.com&xfo=SAMEORIGIN";
i.onload = t.step_func_done(function () {
assert_throws(
"SecurityError",
function () { i.contentDocument.origin },
"The same-origin page was blocked and sandboxed.");
});
document.body.appendChild(i);
}, "A 'frame-ancestors' CSP directive overrides an 'x-frame-options' header which would allow the page.");
</script>
</body>
</html>
#!/usr/bin/perl
use strict;
use CGI;
my $cgi = new CGI;
print "Content-Type: text/html; charset=UTF-8\n";
print "Content-Security-Policy: frame-ancestors " . $cgi->param("policy") . "\n";
print "X-Frame-Options: " . $cgi->param("xfo") . "\n\n";
print "<!DOCTYPE html>\n";
print "<html>\n";
print "<body>\n";
print " <p>This is an IFrame sending a Content Security Policy header containing \"frame-ancestors " . $cgi->param("policy") . "\" and \"X-Frame-Options: " . $cgi->param("xfo") . "\".</p>\n";
print "</body>\n";
print "</html>\n";
...@@ -70,6 +70,7 @@ public: ...@@ -70,6 +70,7 @@ public:
bool didSetReferrerPolicy() const { return m_didSetReferrerPolicy; } bool didSetReferrerPolicy() const { return m_didSetReferrerPolicy; }
bool isReportOnly() const { return m_reportOnly; } bool isReportOnly() const { return m_reportOnly; }
const Vector<String>& reportEndpoints() const { return m_reportEndpoints; } const Vector<String>& reportEndpoints() const { return m_reportEndpoints; }
bool isFrameAncestorsEnforced() const { return m_frameAncestors.get() && !m_reportOnly; }
// Used to copy plugin-types into a plugin document in a nested // Used to copy plugin-types into a plugin document in a nested
// browsing context. // browsing context.
......
...@@ -615,6 +615,15 @@ bool ContentSecurityPolicy::allowAncestors(LocalFrame* frame, const KURL& url, C ...@@ -615,6 +615,15 @@ bool ContentSecurityPolicy::allowAncestors(LocalFrame* frame, const KURL& url, C
return isAllowedByAllWithFrame<&CSPDirectiveList::allowAncestors>(m_policies, frame, url, reportingStatus); return isAllowedByAllWithFrame<&CSPDirectiveList::allowAncestors>(m_policies, frame, url, reportingStatus);
} }
bool ContentSecurityPolicy::isFrameAncestorsEnforced() const
{
for (const auto& policy : m_policies) {
if (policy->isFrameAncestorsEnforced())
return true;
}
return false;
}
bool ContentSecurityPolicy::isActive() const bool ContentSecurityPolicy::isActive() const
{ {
return !m_policies.isEmpty(); return !m_policies.isEmpty();
......
...@@ -176,6 +176,7 @@ public: ...@@ -176,6 +176,7 @@ public:
// because a child frame can't manipulate the URL of a cross-origin // because a child frame can't manipulate the URL of a cross-origin
// parent. // parent.
bool allowAncestors(LocalFrame*, const KURL&, ReportingStatus = SendReport) const; bool allowAncestors(LocalFrame*, const KURL&, ReportingStatus = SendReport) const;
bool isFrameAncestorsEnforced() const;
// The nonce and hash allow functions are guaranteed to not have any side // The nonce and hash allow functions are guaranteed to not have any side
// effects, including reporting. // effects, including reporting.
......
...@@ -92,4 +92,16 @@ TEST_F(ContentSecurityPolicyTest, CopyPluginTypesFrom) ...@@ -92,4 +92,16 @@ TEST_F(ContentSecurityPolicyTest, CopyPluginTypesFrom)
EXPECT_FALSE(csp2->allowPluginType("application/x-type-2", "application/x-type-2", exampleUrl, ContentSecurityPolicy::SuppressReport)); EXPECT_FALSE(csp2->allowPluginType("application/x-type-2", "application/x-type-2", exampleUrl, ContentSecurityPolicy::SuppressReport));
} }
TEST_F(ContentSecurityPolicyTest, IsFrameAncestorsEnforced)
{
csp->didReceiveHeader("script-src 'none';", ContentSecurityPolicyHeaderTypeEnforce, ContentSecurityPolicyHeaderSourceHTTP);
EXPECT_FALSE(csp->isFrameAncestorsEnforced());
csp->didReceiveHeader("frame-ancestors 'self'", ContentSecurityPolicyHeaderTypeReport, ContentSecurityPolicyHeaderSourceHTTP);
EXPECT_FALSE(csp->isFrameAncestorsEnforced());
csp->didReceiveHeader("frame-ancestors 'self'", ContentSecurityPolicyHeaderTypeEnforce, ContentSecurityPolicyHeaderSourceHTTP);
EXPECT_TRUE(csp->isFrameAncestorsEnforced());
}
} // namespace } // namespace
...@@ -469,7 +469,18 @@ void DocumentLoader::responseReceived(Resource* resource, const ResourceResponse ...@@ -469,7 +469,18 @@ void DocumentLoader::responseReceived(Resource* resource, const ResourceResponse
if (response.appCacheID()) if (response.appCacheID())
memoryCache()->remove(m_mainResource.get()); memoryCache()->remove(m_mainResource.get());
m_contentSecurityPolicy = ContentSecurityPolicy::create();
m_contentSecurityPolicy->setOverrideURLForSelf(response.url());
m_contentSecurityPolicy->didReceiveHeaders(ContentSecurityPolicyResponseHeaders(response));
if (!m_contentSecurityPolicy->allowAncestors(m_frame, response.url())) {
cancelLoadAfterXFrameOptionsOrCSPDenied(response);
return;
}
DEFINE_STATIC_LOCAL(AtomicString, xFrameOptionHeader, ("x-frame-options", AtomicString::ConstructFromLiteral)); DEFINE_STATIC_LOCAL(AtomicString, xFrameOptionHeader, ("x-frame-options", AtomicString::ConstructFromLiteral));
// 'frame-ancestors' obviates 'x-frame-options': https://w3c.github.io/webappsec/specs/content-security-policy/#frame-ancestors-and-frame-options
if (!m_contentSecurityPolicy->isFrameAncestorsEnforced()) {
HTTPHeaderMap::const_iterator it = response.httpHeaderFields().find(xFrameOptionHeader); HTTPHeaderMap::const_iterator it = response.httpHeaderFields().find(xFrameOptionHeader);
if (it != response.httpHeaderFields().end()) { if (it != response.httpHeaderFields().end()) {
String content = it->value; String content = it->value;
...@@ -483,13 +494,6 @@ void DocumentLoader::responseReceived(Resource* resource, const ResourceResponse ...@@ -483,13 +494,6 @@ void DocumentLoader::responseReceived(Resource* resource, const ResourceResponse
return; return;
} }
} }
m_contentSecurityPolicy = ContentSecurityPolicy::create();
m_contentSecurityPolicy->setOverrideURLForSelf(response.url());
m_contentSecurityPolicy->didReceiveHeaders(ContentSecurityPolicyResponseHeaders(response));
if (!m_contentSecurityPolicy->allowAncestors(m_frame, response.url())) {
cancelLoadAfterXFrameOptionsOrCSPDenied(response);
return;
} }
ASSERT(!mainResourceLoader() || !mainResourceLoader()->defersLoading()); ASSERT(!mainResourceLoader() || !mainResourceLoader()->defersLoading());
......
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