forked from mirrors/gecko-dev
Bug 515458 - "(CSP) Implement frame ancestor check" [r=jst ui-r=johnath]
This commit is contained in:
parent
629bb5afcc
commit
26ad4e5a5b
13 changed files with 328 additions and 0 deletions
|
|
@ -62,3 +62,4 @@ externalProtocolChkMsg=Remember my choice for all links of this type.
|
|||
externalProtocolLaunchBtn=Launch application
|
||||
malwareBlocked=The site at %S has been reported as an attack site and has been blocked based on your security preferences.
|
||||
phishingBlocked=The web site at %S has been reported as a web forgery designed to trick users into sharing personal or financial information.
|
||||
cspFrameAncestorBlocked=This page has a content security policy that prevents it from being embedded in this way.
|
||||
|
|
|
|||
|
|
@ -163,6 +163,9 @@ be temporary, and you can try again later.</li>
|
|||
<p>These types of web forgeries are used in scams known as phishing attacks, in which fraudulent web pages and emails are used to imitate sources you may trust.</p>
|
||||
">
|
||||
|
||||
<!ENTITY cspFrameAncestorBlocked.title "Blocked by Content Security Policy">
|
||||
<!ENTITY cspFrameAncestorBlocked.longDesc "<p>&brandShortName; prevented this page from loading in this way because the page has a content security policy that disallows it.</p>">
|
||||
|
||||
<!ENTITY securityOverride.linkText "Or you can add an exception…">
|
||||
<!ENTITY securityOverride.getMeOutOfHereButton "Get me out of here!">
|
||||
<!ENTITY securityOverride.exceptionButtonLabel "Add Exception…">
|
||||
|
|
|
|||
|
|
@ -87,4 +87,8 @@
|
|||
#define NS_FINDBROADCASTER_AWAIT_OVERLAYS \
|
||||
NS_ERROR_GENERATE_SUCCESS(NS_ERROR_MODULE_CONTENT, 14)
|
||||
|
||||
/* Error codes for CSP */
|
||||
#define NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION \
|
||||
NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_SECURITY, 99)
|
||||
|
||||
#endif // nsContentErrors_h___
|
||||
|
|
|
|||
|
|
@ -2345,6 +2345,25 @@ nsDocument::InitCSP()
|
|||
#endif
|
||||
}
|
||||
|
||||
// Check for frame-ancestor violation
|
||||
nsCOMPtr<nsIDocShell> docShell = do_QueryReferent(mDocumentContainer);
|
||||
if (docShell) {
|
||||
PRBool safeAncestry = false;
|
||||
|
||||
// PermitsAncestry sends violation reports when necessary
|
||||
rv = mCSP->PermitsAncestry(docShell, &safeAncestry);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (!safeAncestry) {
|
||||
#ifdef PR_LOGGING
|
||||
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
|
||||
("CSP doesn't like frame's ancestry, not loading."));
|
||||
#endif
|
||||
// stop! ERROR page!
|
||||
mChannel->Cancel(NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION);
|
||||
}
|
||||
}
|
||||
|
||||
//Copy into principal
|
||||
nsIPrincipal* principal = GetPrincipal();
|
||||
|
||||
|
|
|
|||
|
|
@ -337,6 +337,10 @@ _TEST_FILES = test_bug5141.html \
|
|||
file_CSP.sjs \
|
||||
file_CSP_main.html \
|
||||
file_CSP_main.js \
|
||||
test_CSP_frameancestors.html \
|
||||
file_CSP_frameancestors.sjs \
|
||||
file_CSP_frameancestors_main.html \
|
||||
file_CSP_frameancestors_main.js \
|
||||
test_bug540854.html \
|
||||
bug540854.sjs \
|
||||
$(NULL)
|
||||
|
|
|
|||
57
content/base/test/file_CSP_frameancestors.sjs
Normal file
57
content/base/test/file_CSP_frameancestors.sjs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
// SJS file for CSP frame ancestor mochitests
|
||||
function handleRequest(request, response)
|
||||
{
|
||||
var query = {};
|
||||
request.queryString.split('&').forEach(function (val) {
|
||||
var [name, value] = val.split('=');
|
||||
query[name] = unescape(value);
|
||||
});
|
||||
|
||||
var isPreflight = request.method == "OPTIONS";
|
||||
|
||||
|
||||
//avoid confusing cache behaviors
|
||||
response.setHeader("Cache-Control", "no-cache", false);
|
||||
|
||||
// grab the desired policy from the query, and then serve a page
|
||||
if (query['csp'])
|
||||
response.setHeader("X-Content-Security-Policy",
|
||||
unescape(query['csp']),
|
||||
false);
|
||||
if (query['scriptedreport']) {
|
||||
// spit back a script that records that the page loaded
|
||||
response.setHeader("Content-Type", "text/javascript", false);
|
||||
response.write('netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");');
|
||||
if (query['double'])
|
||||
response.write('window.parent.parent.parent.frameLoaded("' + query['scriptedreport'] + '", ' +
|
||||
'window.location.toString());');
|
||||
else
|
||||
response.write('window.parent.parent.frameLoaded("' + query['scriptedreport'] + '", ' +
|
||||
'window.location.toString());');
|
||||
} else if (query['internalframe']) {
|
||||
// spit back an internal iframe (one that might be blocked)
|
||||
response.setHeader("Content-Type", "text/html", false);
|
||||
response.write('<html><head>');
|
||||
if (query['double'])
|
||||
response.write('<script src="file_CSP_frameancestors.sjs?double=1&scriptedreport=' + query['testid'] + '"></script>');
|
||||
else
|
||||
response.write('<script src="file_CSP_frameancestors.sjs?scriptedreport=' + query['testid'] + '"></script>');
|
||||
response.write('</head><body>');
|
||||
response.write(unescape(query['internalframe']));
|
||||
response.write('</body></html>');
|
||||
} else if (query['externalframe']) {
|
||||
// spit back an internal iframe (one that won't be blocked, and probably
|
||||
// has no CSP)
|
||||
response.setHeader("Content-Type", "text/html", false);
|
||||
response.write('<html><head>');
|
||||
response.write('</head><body>');
|
||||
response.write(unescape(query['externalframe']));
|
||||
response.write('</body></html>');
|
||||
} else {
|
||||
// default case: error.
|
||||
response.setHeader("Content-Type", "text/html", false);
|
||||
response.write('<html><body>');
|
||||
response.write("ERROR: not sure what to serve.");
|
||||
response.write('</body></html>');
|
||||
}
|
||||
}
|
||||
44
content/base/test/file_CSP_frameancestors_main.html
Normal file
44
content/base/test/file_CSP_frameancestors_main.html
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>CSP frame ancestors tests</title>
|
||||
|
||||
<!-- this page shouldn't have a CSP, just the sub-pages. -->
|
||||
<script src='file_CSP_frameancestors_main.js'></script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- These iframes will get populated by the attached javascript. -->
|
||||
<tt> aa_allow: /* innermost frame allows a */</tt><br/>
|
||||
<iframe id='aa_allow'></iframe><br/>
|
||||
|
||||
<tt> aa_block: /* innermost frame denies a */</tt><br/>
|
||||
<iframe id='aa_block'></iframe><br/>
|
||||
|
||||
<tt> ab_allow: /* innermost frame allows a */</tt><br/>
|
||||
<iframe id='ab_allow'></iframe><br/>
|
||||
|
||||
<tt> ab_block: /* innermost frame denies a */</tt><br/>
|
||||
<iframe id='ab_block'></iframe><br/>
|
||||
|
||||
<tt> aba_allow: /* innermost frame allows b,a */</tt><br/>
|
||||
<iframe id='aba_allow'></iframe><br/>
|
||||
|
||||
<tt> aba_block: /* innermost frame denies b */</tt><br/>
|
||||
<iframe id='aba_block'></iframe><br/>
|
||||
|
||||
<tt> aba2_block: /* innermost frame denies a */</tt><br/>
|
||||
<iframe id='aba2_block'></iframe><br/>
|
||||
|
||||
<tt> abb_allow: /* innermost frame allows b,a */</tt><br/>
|
||||
<iframe id='abb_allow'></iframe><br/>
|
||||
|
||||
<tt> abb_block: /* innermost frame denies b */</tt><br/>
|
||||
<iframe id='abb_block'></iframe><br/>
|
||||
|
||||
<tt> abb2_block: /* innermost frame denies a */</tt><br/>
|
||||
<iframe id='abb2_block'></iframe><br/>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
65
content/base/test/file_CSP_frameancestors_main.js
Normal file
65
content/base/test/file_CSP_frameancestors_main.js
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
// Script to populate the test frames in the frame ancestors mochitest.
|
||||
//
|
||||
function setupFrames() {
|
||||
|
||||
var $ = function(v) { return document.getElementById(v); }
|
||||
var base = {
|
||||
self: '/tests/content/base/test/file_CSP_frameancestors.sjs',
|
||||
a: 'http://localhost:8888/tests/content/base/test/file_CSP_frameancestors.sjs',
|
||||
b: 'http://example.com/tests/content/base/test/file_CSP_frameancestors.sjs'
|
||||
};
|
||||
|
||||
var host = { a: 'http://localhost:8888', b: 'http://example.com:80' };
|
||||
|
||||
var innerframeuri = null;
|
||||
var elt = null;
|
||||
|
||||
elt = $('aa_allow');
|
||||
elt.src = base.a + "?testid=aa_allow&internalframe=aa_a&csp=" +
|
||||
escape("allow 'none'; frame-ancestors " + host.a + "; script-src 'self'");
|
||||
|
||||
elt = $('aa_block');
|
||||
elt.src = base.a + "?testid=aa_block&internalframe=aa_b&csp=" +
|
||||
escape("allow 'none'; frame-ancestors 'none'; script-src 'self'");
|
||||
|
||||
elt = $('ab_allow');
|
||||
elt.src = base.b + "?testid=ab_allow&internalframe=ab_a&csp=" +
|
||||
escape("allow 'none'; frame-ancestors " + host.a + "; script-src 'self'");
|
||||
|
||||
elt = $('ab_block');
|
||||
elt.src = base.b + "?testid=ab_block&internalframe=ab_b&csp=" +
|
||||
escape("allow 'none'; frame-ancestors 'none'; script-src 'self'");
|
||||
|
||||
/* .... two-level framing */
|
||||
elt = $('aba_allow');
|
||||
innerframeuri = base.a + "?testid=aba_allow&double=1&internalframe=aba_a&csp=" +
|
||||
escape("allow 'none'; frame-ancestors " + host.a + " " + host.b + "; script-src 'self'");
|
||||
elt.src = base.b + "?externalframe=" + escape('<iframe src="' + innerframeuri + '"></iframe>');
|
||||
|
||||
elt = $('aba_block');
|
||||
innerframeuri = base.a + "?testid=aba_allow&double=1&internalframe=aba_b&csp=" +
|
||||
escape("allow 'none'; frame-ancestors " + host.a + "; script-src 'self'");
|
||||
elt.src = base.b + "?externalframe=" + escape('<iframe src="' + innerframeuri + '"></iframe>');
|
||||
|
||||
elt = $('aba2_block');
|
||||
innerframeuri = base.a + "?testid=aba_allow&double=1&internalframe=aba2_b&csp=" +
|
||||
escape("allow 'none'; frame-ancestors " + host.b + "; script-src 'self'");
|
||||
elt.src = base.b + "?externalframe=" + escape('<iframe src="' + innerframeuri + '"></iframe>');
|
||||
|
||||
elt = $('abb_allow');
|
||||
innerframeuri = base.b + "?testid=abb_allow&double=1&internalframe=abb_a&csp=" +
|
||||
escape("allow 'none'; frame-ancestors " + host.a + " " + host.b + "; script-src 'self'");
|
||||
elt.src = base.b + "?externalframe=" + escape('<iframe src="' + innerframeuri + '"></iframe>');
|
||||
|
||||
elt = $('abb_block');
|
||||
innerframeuri = base.b + "?testid=abb_allow&double=1&internalframe=abb_b&csp=" +
|
||||
escape("allow 'none'; frame-ancestors " + host.a + "; script-src 'self'");
|
||||
elt.src = base.b + "?externalframe=" + escape('<iframe src="' + innerframeuri + '"></iframe>');
|
||||
|
||||
elt = $('abb2_block');
|
||||
innerframeuri = base.b + "?testid=abb_allow&double=1&internalframe=abb2_b&csp=" +
|
||||
escape("allow 'none'; frame-ancestors " + host.b + "; script-src 'self'");
|
||||
elt.src = base.b + "?externalframe=" + escape('<iframe src="' + innerframeuri + '"></iframe>');
|
||||
}
|
||||
|
||||
window.addEventListener('load', setupFrames, false);
|
||||
120
content/base/test/test_CSP_frameancestors.html
Normal file
120
content/base/test/test_CSP_frameancestors.html
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for Content Security Policy Frame Ancestors directive</title>
|
||||
<script type="text/javascript" src="/MochiKit/packed.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<iframe style="width:100%;height:300px;" id='cspframe'></iframe>
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
var path = "/tests/content/base/test/";
|
||||
|
||||
// These are test results: -1 means it hasn't run,
|
||||
// true/false is the pass/fail result.
|
||||
var framesThatShouldLoad = {
|
||||
aa_allow: -1, /* innermost frame allows a */
|
||||
//aa_block: -1, /* innermost frame denies a */
|
||||
ab_allow: -1, /* innermost frame allows a */
|
||||
//ab_block: -1, /* innermost frame denies a */
|
||||
aba_allow: -1, /* innermost frame allows b,a */
|
||||
//aba_block: -1, /* innermost frame denies b */
|
||||
//aba2_block: -1, /* innermost frame denies a */
|
||||
abb_allow: -1, /* innermost frame allows b,a */
|
||||
//abb_block: -1, /* innermost frame denies b */
|
||||
//abb2_block: -1, /* innermost frame denies a */
|
||||
};
|
||||
|
||||
var expectedViolationsLeft = 6;
|
||||
|
||||
// This is used to watch the blocked data bounce off CSP and allowed data
|
||||
// get sent out to the wire.
|
||||
function examiner() {
|
||||
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
|
||||
this.obsvc = Components.classes['@mozilla.org/observer-service;1']
|
||||
.getService(Components.interfaces.nsIObserverService);
|
||||
this.obsvc.addObserver(this, "csp-on-violate-policy", false);
|
||||
}
|
||||
examiner.prototype = {
|
||||
observe: function(subject, topic, data) {
|
||||
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
|
||||
// subject should be an nsURI, and should be either allowed or blocked.
|
||||
if(!subject.QueryInterface) return;
|
||||
|
||||
if (topic === "csp-on-violate-policy") {
|
||||
//these were blocked... record that they were blocked
|
||||
var uri = subject.QueryInterface(Components.interfaces.nsIURI);
|
||||
window.frameBlocked(uri.asciiSpec, data);
|
||||
}
|
||||
},
|
||||
|
||||
// must eventually call this to remove the listener,
|
||||
// or mochitests might get borked.
|
||||
remove: function() {
|
||||
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
|
||||
this.obsvc.removeObserver(this, "csp-on-violate-policy");
|
||||
}
|
||||
}
|
||||
|
||||
// called when a frame is loaded
|
||||
// -- if it's not enumerated above, it should not load!
|
||||
var frameLoaded = function(testname, uri) {
|
||||
//test already complete.... forget it... remember the first result.
|
||||
if (window.framesThatShouldLoad[testname] != -1)
|
||||
return;
|
||||
|
||||
if (typeof window.framesThatShouldLoad[testname] === 'undefined') {
|
||||
// uh-oh, we're not expecting this frame to load!
|
||||
ok(false, testname + ' framed site should not have loaded: ' + uri);
|
||||
} else {
|
||||
framesThatShouldLoad[testname] = true;
|
||||
ok(true, testname + ' framed site when allowed by csp (' + uri + ')');
|
||||
}
|
||||
checkTestResults();
|
||||
}
|
||||
|
||||
// called when a frame is blocked
|
||||
// -- we can't determine *which* frame was blocked, but at least we can count them
|
||||
var frameBlocked = function(uri, policy) {
|
||||
ok(true, 'a CSP policy blocked frame from being loaded: ' + policy);
|
||||
expectedViolationsLeft--;
|
||||
checkTestResults();
|
||||
}
|
||||
|
||||
|
||||
// Check to see if all the tests have run
|
||||
var checkTestResults = function() {
|
||||
// if any test is incomplete, keep waiting
|
||||
for (var v in framesThatShouldLoad)
|
||||
if(framesThatShouldLoad[v] == -1)
|
||||
return;
|
||||
|
||||
if (expectedViolationsLeft > 0)
|
||||
return;
|
||||
|
||||
// ... otherwise, finish
|
||||
window.examiner.remove();
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// set up and go
|
||||
window.examiner = new examiner();
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
// save this for last so that our listeners are registered.
|
||||
// ... this loads the testbed of good and bad requests.
|
||||
document.getElementById('cspframe').src = 'file_CSP_frameancestors_main.html';
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -3622,6 +3622,11 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI *aURI,
|
|||
formatStrCount = 1;
|
||||
error.AssignLiteral("netTimeout");
|
||||
}
|
||||
else if (NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION == aError) {
|
||||
// CSP error
|
||||
cssClass.AssignLiteral("neterror");
|
||||
error.AssignLiteral("cspFrameAncestorBlocked");
|
||||
}
|
||||
else if (NS_ERROR_GET_MODULE(aError) == NS_ERROR_MODULE_SECURITY) {
|
||||
nsCOMPtr<nsINSSErrorsService> nsserr =
|
||||
do_GetService(NS_NSS_ERRORS_SERVICE_CONTRACTID);
|
||||
|
|
|
|||
|
|
@ -313,6 +313,7 @@
|
|||
<h1 id="et_nssFailure2">&nssFailure2.title;</h1>
|
||||
<h1 id="et_nssBadCert">&nssBadCert.title;</h1>
|
||||
<h1 id="et_malwareBlocked">&malwareBlocked.title;</h1>
|
||||
<h1 id="et_cspFrameAncestorBlocked">&cspFrameAncestorBlocked.title;</h1>
|
||||
</div>
|
||||
<div id="errorDescriptionsContainer">
|
||||
<div id="ed_generic">&generic.longDesc;</div>
|
||||
|
|
@ -335,6 +336,7 @@
|
|||
<div id="ed_nssFailure2">&nssFailure2.longDesc;</div>
|
||||
<div id="ed_nssBadCert">&nssBadCert.longDesc2;</div>
|
||||
<div id="ed_malwareBlocked">&malwareBlocked.longDesc;</div>
|
||||
<div id="ed_cspFrameAncestorBlocked">&cspFrameAncestorBlocked.longDesc;</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -61,3 +61,4 @@ externalProtocolChkMsg=Remember my choice for all links of this type.
|
|||
externalProtocolLaunchBtn=Launch application
|
||||
malwareBlocked=The site at %S has been reported as an attack site and has been blocked based on your security preferences.
|
||||
phishingBlocked=The web site at %S has been reported as a web forgery designed to trick users into sharing personal or financial information.
|
||||
cspFrameAncestorBlocked=This page has a content security policy that prevents it from being embedded in this way.
|
||||
|
|
|
|||
|
|
@ -80,6 +80,9 @@
|
|||
<p>These types of web forgeries are used in scams known as phishing attacks, in which fraudulent web pages and emails are used to imitate sources you may trust.</p>
|
||||
">
|
||||
|
||||
<!ENTITY cspFrameAncestorBlocked.title "Blocked by Content Security Policy">
|
||||
<!ENTITY cspFrameAncestorBlocked.longDesc "<p>The browser prevented this page from loading in this way because the page has a content security policy that disallows it.</p>">
|
||||
|
||||
<!-- Include app-specific error messages - do not change this in localization!
|
||||
Some applications might override netErrorApp.dtd with their specific version,
|
||||
this inclusion needs to be intact for that approach to work correctly. -->
|
||||
|
|
|
|||
Loading…
Reference in a new issue