Bug 824652 - crypto.generateCRMFRequest bypasses CSP (allows script execution from a string, without unsafe-eval) r=bsmith r=khuey r=keeler

This commit is contained in:
David Dahl 2013-08-06 21:46:05 -05:00
parent 211068c91a
commit 2d55ce87ec
22 changed files with 342 additions and 36 deletions

View File

@ -44,6 +44,7 @@ class nsIChannel;
class nsIConsoleService;
class nsIContent;
class nsIContentPolicy;
class nsIContentSecurityPolicy;
class nsIDocShell;
class nsIDocument;
class nsIDocumentLoaderFactory;
@ -471,6 +472,12 @@ public:
return sSecurityManager;
}
/**
* Get the ContentSecurityPolicy for a JS context.
**/
static bool GetContentSecurityPolicy(JSContext* aCx,
nsIContentSecurityPolicy** aCSP);
// Returns the subject principal. Guaranteed to return non-null. May only
// be called when nsContentUtils is initialized.
static nsIPrincipal* GetSubjectPrincipal();

View File

@ -6026,6 +6026,34 @@ nsContentUtils::FindInternalContentViewer(const char* aType,
return nullptr;
}
bool
nsContentUtils::GetContentSecurityPolicy(JSContext* aCx,
nsIContentSecurityPolicy** aCSP)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
// Get the security manager
nsCOMPtr<nsIScriptSecurityManager> ssm = nsContentUtils::GetSecurityManager();
if (!ssm) {
NS_ERROR("Failed to get security manager service");
return false;
}
nsCOMPtr<nsIPrincipal> subjectPrincipal = ssm->GetCxSubjectPrincipal(aCx);
NS_ASSERTION(subjectPrincipal, "Failed to get subjectPrincipal");
nsCOMPtr<nsIContentSecurityPolicy> csp;
nsresult rv = subjectPrincipal->GetCsp(getter_AddRefs(csp));
if (NS_FAILED(rv)) {
NS_ERROR("CSP: Failed to get CSP from principal.");
return false;
}
csp.forget(aCSP);
return true;
}
// static
bool
nsContentUtils::IsPatternMatching(nsAString& aValue, nsAString& aPattern,

View File

@ -364,6 +364,18 @@ MOCHITEST_FILES_B = \
file_CSP_evalscript_main_spec_compliant.html^headers^ \
file_CSP_evalscript_main_spec_compliant_allowed.html \
file_CSP_evalscript_main_spec_compliant_allowed.html^headers^ \
test_CSP_evalscript_getCRMFRequest.html \
file_CSP_evalscript_main_getCRMFRequest.html \
file_CSP_evalscript_main_getCRMFRequest.html^headers^ \
file_CSP_evalscript_main_getCRMFRequest.js \
file_CSP_evalscript_main_allowed_getCRMFRequest.js \
file_CSP_evalscript_main_spec_compliant_getCRMFRequest.html \
file_CSP_evalscript_main_spec_compliant_getCRMFRequest.html^headers^ \
file_CSP_evalscript_main_spec_compliant_allowed_getCRMFRequest.html \
file_CSP_evalscript_main_spec_compliant_allowed_getCRMFRequest.html^headers^ \
file_CSP_evalscript_no_CSP_at_all.html \
file_CSP_evalscript_no_CSP_at_all.html^headers^ \
file_CSP_evalscript_no_CSP_at_all.js \
test_CSP_inlinestyle.html \
file_CSP_inlinestyle_main.html \
file_CSP_inlinestyle_main.html^headers^ \

View File

@ -30,7 +30,6 @@ var onevalblocked = (function(window) {
// Defer until document is loaded so that we can write the pretty result boxes
// out.
addEventListener('load', function() {
// setTimeout(String) test -- mutate something in the window._testResults
// obj, then check it.
{

View File

@ -28,7 +28,6 @@ var onevalblocked = (function(window) {
// Defer until document is loaded so that we can write the pretty result boxes
// out.
addEventListener('load', function() {
// setTimeout(String) test -- should pass
try {
setTimeout('onevalexecuted(true, "setTimeout(String)", "setTimeout with a string was enabled.");', 10);

View File

@ -0,0 +1,42 @@
// some javascript for the CSP eval() tests
// all of these evals should succeed, as the document loading this script
// has script-src 'self' 'unsafe-eval'
function logResult(str, passed) {
var elt = document.createElement('div');
var color = passed ? "#cfc;" : "#fcc";
elt.setAttribute('style', 'background-color:' + color + '; width:100%; border:1px solid black; padding:3px; margin:4px;');
elt.innerHTML = str;
document.body.appendChild(elt);
}
// callback for when stuff is allowed by CSP
var onevalexecuted = (function(window) {
return function(shouldrun, what, data) {
window.parent.scriptRan(shouldrun, what, data);
logResult((shouldrun ? "PASS: " : "FAIL: ") + what + " : " + data, shouldrun);
};})(window);
// callback for when stuff is blocked
var onevalblocked = (function(window) {
return function(shouldrun, what, data) {
window.parent.scriptBlocked(shouldrun, what, data);
logResult((shouldrun ? "FAIL: " : "PASS: ") + what + " : " + data, !shouldrun);
};})(window);
// Defer until document is loaded so that we can write the pretty result boxes
// out.
addEventListener('load', function() {
// test that allows crypto.generateCRMFRequest eval to run
try {
var script =
'console.log("dynamic script passed to crypto.generateCRMFRequest should execute")';
crypto.generateCRMFRequest('CN=0', 0, 0, null, script, 384, null, 'rsa-dual-use');
onevalexecuted(true, "eval(script) inside crypto.generateCRMFRequest",
"eval executed during crypto.generateCRMFRequest");
} catch (e) {
onevalblocked(true, "eval(script) inside crypto.generateCRMFRequest",
"eval was blocked during crypto.generateCRMFRequest");
}
}, false);

View File

@ -0,0 +1,12 @@
<html>
<head>
<title>CSP eval script tests</title>
<script type="application/javascript"
src="file_CSP_evalscript_main_getCRMFRequest.js"></script>
</head>
<body>
Foo.
</body>
</html>

View File

@ -0,0 +1,2 @@
Cache-Control: no-cache
X-Content-Security-Policy: default-src 'self'

View File

@ -0,0 +1,48 @@
// some javascript for the CSP eval() tests
function logResult(str, passed) {
var elt = document.createElement('div');
var color = passed ? "#cfc;" : "#fcc";
elt.setAttribute('style', 'background-color:' + color + '; width:100%; border:1px solid black; padding:3px; margin:4px;');
elt.innerHTML = str;
document.body.appendChild(elt);
}
window._testResults = {};
// callback for when stuff is allowed by CSP
var onevalexecuted = (function(window) {
return function(shouldrun, what, data) {
window._testResults[what] = "ran";
window.parent.scriptRan(shouldrun, what, data);
logResult((shouldrun ? "PASS: " : "FAIL: ") + what + " : " + data, shouldrun);
};})(window);
// callback for when stuff is blocked
var onevalblocked = (function(window) {
return function(shouldrun, what, data) {
window._testResults[what] = "blocked";
window.parent.scriptBlocked(shouldrun, what, data);
logResult((shouldrun ? "FAIL: " : "PASS: ") + what + " : " + data, !shouldrun);
};})(window);
// Defer until document is loaded so that we can write the pretty result boxes
// out.
addEventListener('load', function() {
// generateCRMFRequest test -- make sure we cannot eval the callback if CSP is in effect
try {
var script = 'console.log("dynamic script eval\'d in crypto.generateCRMFRequest should be disallowed")';
crypto.generateCRMFRequest('CN=0', 0, 0, null, script, 384, null, 'rsa-dual-use');
onevalexecuted(false, "crypto.generateCRMFRequest()",
"crypto.generateCRMFRequest() should not run!");
} catch (e) {
onevalblocked(false, "eval(script) inside crypto.generateCRMFRequest",
"eval was blocked during crypto.generateCRMFRequest");
}
}, false);

View File

@ -0,0 +1,12 @@
<html>
<head>
<title>CSP eval script tests</title>
<script type="application/javascript"
src="file_CSP_evalscript_main_allowed_getCRMFRequest.js"></script>
</head>
<body>
Foo.
</body>
</html>

View File

@ -0,0 +1,2 @@
Cache-Control: no-cache
Content-Security-Policy: default-src 'self' ; script-src 'self' 'unsafe-eval'

View File

@ -0,0 +1,12 @@
<html>
<head>
<title>CSP eval script tests</title>
<script type="application/javascript"
src="file_CSP_evalscript_main_getCRMFRequest.js"></script>
</head>
<body>
Foo.
</body>
</html>

View File

@ -0,0 +1,2 @@
Cache-Control: no-cache
Content-Security-Policy: default-src 'self'

View File

@ -0,0 +1,12 @@
<html>
<head>
<title>CSP eval script tests: no CSP specified</title>
<script type="application/javascript"
src="file_CSP_evalscript_no_CSP_at_all.js"></script>
</head>
<body>
Foo. See bug 824652
</body>
</html>

View File

@ -0,0 +1 @@
Cache-Control: no-cache

View File

@ -0,0 +1,42 @@
// some javascript for the CSP eval() tests
// all of these evals should succeed, as the document loading this script
// has script-src 'self' 'unsafe-eval'
function logResult(str, passed) {
var elt = document.createElement('div');
var color = passed ? "#cfc;" : "#fcc";
elt.setAttribute('style', 'background-color:' + color + '; width:100%; border:1px solid black; padding:3px; margin:4px;');
elt.innerHTML = str;
document.body.appendChild(elt);
}
// callback for when stuff is allowed by CSP
var onevalexecuted = (function(window) {
return function(shouldrun, what, data) {
window.parent.scriptRan(shouldrun, what, data);
logResult((shouldrun ? "PASS: " : "FAIL: ") + what + " : " + data, shouldrun);
};})(window);
// callback for when stuff is blocked
var onevalblocked = (function(window) {
return function(shouldrun, what, data) {
window.parent.scriptBlocked(shouldrun, what, data);
logResult((shouldrun ? "FAIL: " : "PASS: ") + what + " : " + data, !shouldrun);
};})(window);
// Defer until document is loaded so that we can write the pretty result boxes
// out.
addEventListener('load', function() {
// test that allows crypto.generateCRMFRequest eval to run when there is no CSP at all in place
try {
var script =
'console.log("dynamic script passed to crypto.generateCRMFRequest should execute")';
crypto.generateCRMFRequest('CN=0', 0, 0, null, script, 384, null, 'rsa-dual-use');
onevalexecuted(true, "eval(script) inside crypto.generateCRMFRequest: no CSP at all",
"eval executed during crypto.generateCRMFRequest where no CSP is set at all");
} catch (e) {
onevalblocked(true, "eval(script) inside crypto.generateCRMFRequest",
"eval was blocked during crypto.generateCRMFRequest");
}
}, false);

View File

@ -61,6 +61,7 @@ SpecialPowers.pushPrefEnv(
document.getElementById('cspframe').src = 'file_CSP_evalscript_main.html';
document.getElementById('cspframe2').src = 'file_CSP_evalscript_main_spec_compliant.html';
document.getElementById('cspframe3').src = 'file_CSP_evalscript_main_spec_compliant_allowed.html';
// document.getElementById('cspframe4').src = 'file_CSP_evalscript_no_CSP_at_all.html';
});
</script>
</pre>

View File

@ -0,0 +1,70 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for Content Security Policy "no eval" in crypto.getCRMFRequest()</title>
<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>
<iframe style="width:100%;height:300px;" id='cspframe2'></iframe>
<iframe style="width:100%;height:300px;" id='cspframe3'></iframe>
<iframe style="width:100%;height:300px;" id='cspframe4'></iframe>
<script class="testbody" type="text/javascript">
var path = "/tests/content/base/test/";
var evalScriptsThatRan = 0;
var evalScriptsBlocked = 0;
var evalScriptsTotal = 4;
// called by scripts that run
var scriptRan = function(shouldrun, testname, data) {
evalScriptsThatRan++;
ok(shouldrun, 'EVAL SCRIPT RAN: ' + testname + '(' + data + ')');
checkTestResults();
}
// called when a script is blocked
var scriptBlocked = function(shouldrun, testname, data) {
evalScriptsBlocked++;
ok(!shouldrun, 'EVAL SCRIPT BLOCKED: ' + testname + '(' + data + ')');
checkTestResults();
}
// Check to see if all the tests have run
var checkTestResults = function() {
// if any test is incomplete, keep waiting
if (evalScriptsTotal - evalScriptsBlocked - evalScriptsThatRan > 0)
return;
// ... otherwise, finish
SimpleTest.finish();
}
//////////////////////////////////////////////////////////////////////
// set up and go
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv(
{'set':[["security.csp.speccompliant", true]]},
function() {
// 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_evalscript_main_getCRMFRequest.html';
document.getElementById('cspframe2').src = 'file_CSP_evalscript_main_spec_compliant_getCRMFRequest.html';
document.getElementById('cspframe3').src = 'file_CSP_evalscript_main_spec_compliant_allowed_getCRMFRequest.html';
document.getElementById('cspframe4').src = 'file_CSP_evalscript_no_CSP_at_all.html';
});
</script>
</pre>
</body>
</html>

View File

@ -2729,7 +2729,7 @@ WorkerPrivate::Create(JSContext* aCx, JS::Handle<JSObject*> aObj, WorkerPrivate*
return nullptr;
}
if (!GetContentSecurityPolicy(aCx, getter_AddRefs(csp))) {
if (!nsContentUtils::GetContentSecurityPolicy(aCx, getter_AddRefs(csp))) {
return nullptr;
}
@ -4375,34 +4375,6 @@ WorkerPrivate::GetCrossThreadDispatcher()
return mCrossThreadDispatcher;
}
bool
WorkerPrivate::GetContentSecurityPolicy(JSContext* aCx,
nsIContentSecurityPolicy** aCSP)
{
AssertIsOnMainThread();
// Get the security manager
nsCOMPtr<nsIScriptSecurityManager> ssm = nsContentUtils::GetSecurityManager();
if (!ssm) {
NS_ERROR("Failed to get security manager service");
return false;
}
nsCOMPtr<nsIPrincipal> subjectPrincipal = ssm->GetCxSubjectPrincipal(aCx);
NS_ASSERTION(subjectPrincipal, "Failed to get subjectPrincipal");
nsCOMPtr<nsIContentSecurityPolicy> csp;
nsresult rv = subjectPrincipal->GetCsp(getter_AddRefs(csp));
if (NS_FAILED(rv)) {
NS_ERROR("CSP: Failed to get CSP from principal.");
return false;
}
csp.forget(aCSP);
return true;
}
void
WorkerPrivate::BeginCTypesCall()
{

View File

@ -909,10 +909,6 @@ private:
nsCOMPtr<nsIContentSecurityPolicy>& aCSP, bool aEvalAllowed,
bool aReportCSPViolations, bool aXHRParamsAllowed);
static bool
GetContentSecurityPolicy(JSContext *aCx,
nsIContentSecurityPolicy** aCsp);
bool
Dispatch(WorkerRunnable* aEvent, EventQueue* aQueue);

View File

@ -44,11 +44,13 @@
#include "nsIPrompt.h"
#include "nsIFilePicker.h"
#include "nsJSPrincipals.h"
#include "nsJSUtils.h"
#include "nsIPrincipal.h"
#include "nsIScriptSecurityManager.h"
#include "nsIGenKeypairInfoDlg.h"
#include "nsIDOMCryptoDialogs.h"
#include "nsIFormSigningDialog.h"
#include "nsIContentSecurityPolicy.h"
#include "jsapi.h"
#include "jsdbgapi.h"
#include <ctype.h>
@ -1894,6 +1896,39 @@ nsCrypto::GenerateCRMFRequest(JSContext* aContext,
return nullptr;
}
nsCOMPtr<nsIContentSecurityPolicy> csp;
if (!nsContentUtils::GetContentSecurityPolicy(aContext, getter_AddRefs(csp))) {
NS_ERROR("Error: failed to get CSP");
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
bool evalAllowed = true;
bool reportEvalViolations = false;
if (csp && NS_FAILED(csp->GetAllowsEval(&reportEvalViolations, &evalAllowed))) {
NS_WARNING("CSP: failed to get allowsEval");
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
if (reportEvalViolations) {
NS_NAMED_LITERAL_STRING(scriptSample, "window.crypto.generateCRMFRequest: call to eval() or related function blocked by CSP");
const char *fileName;
uint32_t lineNum;
nsJSUtils::GetCallingLocation(aContext, &fileName, &lineNum);
csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL,
NS_ConvertASCIItoUTF16(fileName),
scriptSample,
lineNum);
}
if (!evalAllowed) {
NS_WARNING("eval() not allowed by Content Security Policy");
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
//Put up some UI warning that someone is trying to
//escrow the private key.
//Don't addref this copy. That way ths reference goes away
@ -2042,7 +2077,6 @@ nsCrypto::GenerateCRMFRequest(JSContext* aContext,
return crmf.forget();
}
// Reminder that we inherit the memory passed into us here.
// An implementation to let us back up certs as an event.
nsP12Runnable::nsP12Runnable(nsIX509Cert **certArr, int32_t numCerts,

View File

@ -17,6 +17,7 @@
"content/base/test/test_CSP.html": "TIMED_OUT",
"content/base/test/test_CSP_frameancestors.html": "",
"content/base/test/test_CSP_inlinescript.html": "",
"content/base/test/test_CSP_evalscript_getCRMFRequest.html": "bug 824652",
"content/base/test/test_csp_redirects.html": "TIMED_OUT",
"content/base/test/test_fileapi_slice.html": "bug 775227",
"content/base/test/test_mozfiledataurl.html": "TIMED_OUT",