Introduction
In Part 1 of the article, we looked at how whitelisting trusted source can prevent the malicious code on untrusted site from executing. In this second part of the article, we delve into use of unsafe-inline
, unsafe-eval
keywords in CSP 1 and together with nonce and hashing, introduced in CSP 2, to refrain from ever using unsafe-inline
by enabling only trusted inline code to execute. Please note that keywords like unsafe-inline
and unsafe-eval
have to be enclosed in single quotes.
unsafe-inline
Sometimes, the webpage has come with some inline JavaScript or stylesheet and for enormous amount of work involved, it is not feasible to externalize them in a separate file. This is where unsafe-inline
comes into the picture. For this example, we have an HTML that displays time periodically.
<html>
<head>
<title>unsafe-line in Action</title>
<head>
<body>
<p id="time"></p>
<script type="text/javascript">
function displayTime()
{
var d = new Date();
var n = d.toLocaleTimeString();
document.getElementById('time').innerHTML = n;
setTimeout(function () {
displayTime()
}, 500);
}
displayTime();
</script>
</body>
</html>
Copy the HTML and save it in an HTML file. And open to view that HTML on web browser. We can see that time is displayed. Your time, most likely, is different from mine. Let's add a CSP <meta>
tag in the <head>
section.
6:32:32 PM
<meta http-equiv="Content-Security-Policy" content="default-src 'self';">
Bam! Time does not display and now we have an error. This is the error I got on Chrome.
Refused to execute inline script because it violates the following Content Security Policy directive: "default-src 'self'". Either the 'unsafe-inline' keyword, a hash ('sha256-TVjy1frkE+v+8vB4X884wNJ7xy5bKc32l3WYqLZZ44o='), or a nonce ('nonce-...') is required to enable inline execution. Note also that 'script-src' was not explicitly set, so 'default-src' is used as a fallback.
Let's enable our inline code with unsafe-inline
.
<meta http-equiv="Content-Security-Policy" content="default-src 'self';
script-src 'unsafe-inline';">
This time around, the HTML displays the time.
6:35:32 PM
Nonce and Hash to the Rescue
unsafe-inline
is an all or nothing solution which leaves much to be desired. When unsafe-inline
is enabled, there is a risk that we are also enabling maliciously injected code.
nonce and hashing are introduced in CSP 2 to address this gaping security hole exposed by unsafe-inline. How they work, is they are enabling JavaScript or CSS section with the same nonce value or correct cryptographic hash to execute. Nonce and hash have to be enclosed in single quotes. Remember nonce is used-only-once base64
encoded number that needs to be updated on every page fetch. As long as the nonce in CSP and script/style section matches, the JavaScript or CSS is allowed. Below are the script-src
nonce and style-src
nonce examples.
<meta http-equiv="Content-Security-Policy" content="default-src 'self';
script-src 'nonce-2726c7f26c';">
<script nonce="2726c7f26c">
</script>
<meta http-equiv="Content-Security-Policy" content="default-src 'self';
style-src 'nonce-5823c7f85c';">
<style nonce="5823c7f85c">
</style>
Cryptographic hashing works by calculating the cryptographic message digest of inline code inclusive of their whitespaces and then encoded the hash in base64
format. For a Chrome user, you are lucky because Chrome calculates this hash for you when showing the error on the developer console. I reproduce the above error here again.
Refused to execute inline script because it violates the following Content Security Policy directive: "default-src 'self'". Either the 'unsafe-inline' keyword, a hash ('sha256-TVjy1frkE+v+8vB4X884wNJ7xy5bKc32l3WYqLZZ44o='), or a nonce ('nonce-...') is required to enable inline execution. Note also that 'script-src' was not explicitly set, so 'default-src' is used as a fallback.
All you need to do is to fix the error is to copy SHA256 hash to the script-src
directive.
<meta http-equiv="Content-Security-Policy" content="default-src 'self';
script-src 'sha256-TVjy1frkE+v+8vB4X884wNJ7xy5bKc32l3WYqLZZ44o=';">
<script>
</script>
The same concept works for style section.
<meta http-equiv="Content-Security-Policy" content="default-src 'self';
style-src 'sha256-pkvqLyskjufPOv5VOGnLcoqyD2oDwsfaPxxvXCQdq9Y=';">
<style>
</style>
Viola, the error is gone and time display is back!
Nonce versus Cryptographic Hash
Given the choice between nonce and cryptographic hash, what would be the preferred approach? For the latter, hash has to be recalculated whenever the code is updated while the former requires a carefully-designed random nonce generation policy to ensure the nonce is not easily guessable.
Cryptographic Hashing for External JS and CSS
The same cryptographic hashing approach can be done for external JavaScript and CSS file with Subresource Integrity (SRI). Subresource Integrity is a security feature that enables browsers to verify that fetched resources (for example, from a CDN) are delivered without modification. To use SRI, just compute hash and encode the hash in base64
format and add in under integrity
attribute of the script
or style
tag. And remember to enable the require-sri-for
directive for JS or CSS respectively as shown below:
Content-Security-Policy: require-sri-for script;
<script src="https://mysite.com/example.js"
integrity="sha256-z3Jh8vsefHG06czVpaDhfkrP3AWuSL0aXdJizHFZ/gM="
crossorigin="anonymous"></script>
Content-Security-Policy: require-sri-for style;
<link href="https://mysite.com/example.css" rel="stylesheet" type="text/css"
integrity="sha256-tbqu6h2Qu6rhJtNtkUI6XbYtkzEby9zQFP4DlGIqYdQ="
crossorigin="anonymous">
When the script or stylesheet doesn’t match its integrity value, the browser shall refuse to execute the script or apply the stylesheet.
unsafe-eval
Sometimes, a legacy library cannot be easily modified and is using eval()
to dynamically generate JavaScript code. In this case, the resolution is either if feasible, a library replacement or, as a last resort, allowing of dynamic JavaScript code through unsafe-eval
keyword.
Clickjacking Prevention
Clickjacking is a malicious technique of tricking a user into clicking on something different (usually invisible) from what the user can see. For instance, a web page is overlapped with an iframe
whose opacity set to zero, when the user clicks a legitimate link, unbeknownst to him, he is clicking a link or button on that invisible iframe. CSP 2 introduces frame-ancestors
directive to whitelist URL(s) that is permitted to embed your webpage.
Upgrade Requests from HTTP to HTTPS
By setting the upgrade-insecure-requests
directive, web browser is instructed to fetch all resources using HTTPS scheme. Another directive, block-all-mixed-content
forbids loading any assets using HTTP when the page is loaded using HTTPS. In practice, you only need to set either upgrade-insecure-requests
or block-all-mixed-content
but not both.
Zero Risk CSP: Report-Only
There is an inherent risk in CSP whitelisting approach where a legitimate source of content is overlooked and omitted in CSP, causing some functionality to break. This is simply unacceptable. In CSP 2, enforcement can be turned off and switched to report-only mode by renaming Content-Security-Policy
to Content-Security-Policy-Report-Only
and remember to add report-uri
and report-to
directive for report destination. Note that report-uri
and report-to
can also be added to normal violation blocking Content-Security-Policy
as well.
Why is there a need to specify 2 directives that point to the same report destination? To keep the long story short, report-uri
has been renamed to report-to
in CSP 3 but at the time of article writing, no web browser supports report-to
directive yet. To future-proof your CSP, it is better to specify report-to
in addition to report-uri
. A point for developer to note is these report directives are not supported in the <meta>
element, meaning it has to be specified in the CSP response header. Violation report is sent in JSON format by HTTP POST method. Whenever there is a violation report, it could mean one of the two things, a trusted source is not whitelisted or the webpage is having XSS attacks.
Conclusion
In this final instalment of the 2-part article, we have looked at unsafe-inline
and unsafe-eval
to enabling inline code and dynamic code generation. And we also looked at how nonce and cryptographic hashing help developers to run only their own code. We briefly looked at clickjacking prevention, upgrading request to HTTPS and report-only CSP where CSP is not enforced.