Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

What Web Developers Need to Know About Content Security Policy - Part 2

5.00/5 (3 votes)
15 Apr 2019CPOL6 min read 27.3K   80  
This article continues the content security policy discussion with unsafe-inline, unsafe-eval, nonce, cryptographic hashing and more.

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
<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

HTML
<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.

HTML
<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.

HTML
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; 
      script-src 'nonce-2726c7f26c';">

<script nonce="2726c7f26c">
// code remains unchanged, so it is not shown.
</script>
HTML
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; 
      style-src 'nonce-5823c7f85c';">

<style nonce="5823c7f85c">
// CSS code not shown
</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.

HTML
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; 
      script-src 'sha256-TVjy1frkE+v+8vB4X884wNJ7xy5bKc32l3WYqLZZ44o=';">

<script>
// code remains unchanged, so it is not shown.
</script>

The same concept works for style section.

HTML
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; 
      style-src 'sha256-pkvqLyskjufPOv5VOGnLcoqyD2oDwsfaPxxvXCQdq9Y=';">

<style>
// CSS code not shown
</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:

HTML
Content-Security-Policy: require-sri-for script;

<script src="https://mysite.com/example.js"
        integrity="sha256-z3Jh8vsefHG06czVpaDhfkrP3AWuSL0aXdJizHFZ/gM="
        crossorigin="anonymous"></script>
HTML
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)