While hardening my CSP by removing all unsafe-inline scripts by giving them a nonce, I ran into a problem on a particular page of my website. I edited everything to make it more easily understood on a small scale scenario.
product.php loads the template for which the product will be displayed.
<div id="productDisplay"></div>
<script nonce="sampleCorrectNonce" type="text/javascript">
product();
</script>
product.php?id=123 loads the actual product data and needs to be refreshed with new information through different scenarios.
if ($_GET['action'] === 'displayProduct') {
echo '
samleData
<script nonce="sampleCorrectNonce" type="text/javascript">
productReady();
productOptions();
</script>
';
}
On product.php there is an initial javascript code that fires and loads product.php?id=123 into an DIV by ID.
product = function() {
$("#productDisplay").load("//"+ document.domain + "/shop/product.php?action=displayProduct&id=123");
}
A nonce has been created to allow CSP on inline scripts. Despite the fact that the nonce matches exactly and the < script > is presented inline on the same page, it still triggers an error.
<html>
<body>
<div id="productTemplateStuffa"></div>
<div id="productDisplay">
samleData
<script nonce="sampleCorrectNonce" type="text/javascript">
productReady();
productOptions();
</script>
</div>
<div id="productTemplateStuffb"></div>
<script nonce="sampleCorrectNonce" type="text/javascript">
product();
</script>
</body>
</html>
For some reason, I cannot figure out how to include PHP echoed content from jQuery's load into a DIV while keeping CSP happy.
Firefox's Console Error:
Content Security Policy: The page’s settings blocked the loading of a resource at self (“script-src https://example.loc/ 'nonce-sampleCorrectNonce' 'unsafe-inline' 'unsafe-eval'”). Source: productReady(); ....
This is essentially what the header is sending, I edited it down to keep it simple and hide all external content.
Content-Security-Policy: base-uri 'self'; default-src 'self'; connect-src 'self'; font-src 'self'; form-action 'self'; frame-ancestors 'none'; img-src 'self' data:; media-src 'none'; object-src 'none'; script-src 'self' https://example.loc/ 'nonce-sampleCorrectNonce' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; upgrade-insecure-requests
Removing this fixes the error...
<script nonce="sampleCorrectNonce" type="text/javascript">
productReady();
productOptions();
</script>
Doing this, does NOT fix the error...
<script nonce="sampleCorrectNonce" type="text/javascript">
</script>
This is how Chrome, above, and Firefox, below, shows the inline script content.
<script nonce="" type="text/javascript" src="https://example.loc/theme/assets/global.jquery.min.js?v=1540058004"></script>
<script nonce="sampleCorrectNonce" type="text/javascript" src="//example.loc/theme/assets/global.jquery.min.js?v=1540058004"></script>
Deleting everything inside the script tag, still triggers the error. OK, since it's triggering a GET method, it's setting new headers with a new nonce. If I didn't have the script tags on the GET page, it wouldn't be a problem. I have an unsolvable problem, since the content is dynamic, a hash won't even work.
Doing this on the GET page, allows the headers to match the nonce from the GET request but now viewing source shows mismatched nonces, expectedly. Which means the CSP will trigger still.
<script nonce="' . Headers::getInstance()->getScriptHash() . '" type="text/javascript">
I apologize for the length of this post. I solved the problem by simply rewriting the code so there is no inline script on the GET request. This is what you should be doing anyway and is probably why they designed CSP to work this way.
product.php
has a nonce from another roundtrip? Did you check if all your nonces in your source are identical with the nonce in your header at any given client side moment? – markus