What Web Developers Need to Know About Content Security Policy

Introduction

Content Security Policy (CSP) is a computer security standard introduced by the World Wide Web Consortium (W3C) to prevent cross-site scripting (XSS) and clickjacking attacks. Explained simply, CSP is a whitelist of origins of content that is allowed to load or execute on a webpage. We’ll look at the three versions of CSP and the relevant features of each, though it’s important to note CSP Level 3 is not yet ratified as a W3C recommendation and is still a working draft in progress. It is still subject to change from time to time before its standardization. As we go along, the differences between these versions will be pointed out to you.

What is Cross-Site Scripting?

Cross-Site Scripting (XSS) attacks are a type of code injection, in which malicious scripts are injected into trusted websites. A good example could occur on an ecommerce site: a buyer posts a product review with malicious code that is saved on the server. For every customer who views the product review, malicious code gets executed.

CSP in Action

CSP can be specified in an HTTP response header. When a web client, like a web browser, requests a resource from web server, it sends an HTTP request with a bunch of information in a request header for the server. If the request is successful, the web server then replies back with the resource together with a response header telling the web browser how to handle the response. In the case of CSP, it is specifying what those trusted sources are to fetch the web page content from. On CSP 2 capable browsers, we have an additional option of specifying the CSP in an HTML meta tag. For our examples, this is exactly what we are going to use; we take a web framework agnostic approach to keep things simple. All you need to follow the examples is a text editor and modern web browser.

Anatomy of CSP

CSP begins with Content-Security-Policy text, which is followed by one or more directives. Each directive ends with a semicolon, which can be the beginning of the next directive. Each directive could have zero or multiple values. The values are separated by whitespace. More often than not, the value is simply a trusted source URI.

Content-Security-Policy [directive] <value>;

This is an example of a one-directive CSP. The default-src directive with a ‘self’ value instructs the web browser to only trust content from the same origin as the webpage.

Content-Security-Policy default-src 'self';

The equivalent CSP in a meta tag is shown below:

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

Take note that the meta tag has to be specified within the head section, not the body section of the HTML. One big downside a developer has to be cautious of: with the meta tag approach, CSP rules are not enforced until the meta tag is read and processed.

This is the HTML that loads the image from CodeProject without CSP. You can copy and paste the code in an empty HTML file and save it locally.

<html>
<head>
<title>CSP
in Action</title>
<head>
<body>
<p><img
src="https://www.codeproject.com/App_Themes/CodeProject/Img/logo250x135.gif"
/>  </p>
</body>
</html>

View the HTML on the browser by double-clicking the file on the File Explorer, the image is downloaded and displayed from CodeProject.

image001

Let’s add a CSP meta tag.

<html>
<head>
<meta
http-equiv="Content-Security-Policy" content="default-src
'self';">
<title>CSP
in Action</title>
<head>
<body>
<p><img
src="https://www.codeproject.com/App_Themes/CodeProject/Img/logo250x135.gif"
/>  </p>
</body>
</html>

Now try viewing the page in a browser:

image002

Bam! Now the broken image is shown to indicate the image is not fetched because http://www.codeproject.com is not the same origin domain. Hit F12 on the web browser to open developer tool and navigate to console tab. On Chrome, it shows this error in red.

Refused to load the image ‘https://www.codeproject.com/App_Themes/CodeProject/Img/logo250x135.gif&#8217; because it violates the following Content Security Policy directive: “default-src ‘self'”. Note that ‘img-src’ was not explicitly set, so ‘default-src’ is used as a fallback.

What we have effectively done with the default-src directive is to restrict all the content to the same origin with the ‘self’ keyword, as explained previously.

Let’s append default-src with a whitespace and followed by the CodeProject URI. For simplicity, I just show the updated meta tag as the rest of HTML remains unchanged.

<meta http-equiv="Content-Security-Policy" content="default-src 'self'
https://www.codeproject.com;">

View the page again. Now the CodeProject image is shown. Note: The self keyword has to be enclosed in single quotes while the URI is not required to be.

image001

View the HTML on browser. Now the image is back. Since the gif is an image resource, let’s do some refactoring and put CodeProject URI under the img-src directive.

<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src 
https://www.codeproject.com;">

image001

View the page on browser again. The image still appears. Prior to that, img-src is not specified. What is its value then? The answer is, when not specified, it inherits from default-src. Note: If your URI redirects to a URI on another domain, that domain has to be in the CSP as well.

CSP Directives

CSP directives mostly cover the content type whose source(s) can be specified. This article covers most of the directives. All the directives that fall back to the default-src are shown on the hierarchy below.

image003

  • default-src: Is a main fallback for the other fetch directives when they are not explicitly specified
  • child-src: Lists the trusted sources for web workers and nested browsing contexts loaded using elements such as <frame> and <iframe>. This directive is deprecated in CSP 3. Instead of child-src, to list trusted source for nested browsing contexts and workers, the frame-src and worker-src directives should be used respectively.
  • script-src: Lists trusted sources for JavaScript
  • object-src: Lists trusted sources for the <object>, , and <applet> elements
  • style-src: Lists trusted sources for stylesheets (CSS)
  • img-src: Lists trusted sources of images and favicons
  • media-src: Lists trusted sources for loading media using the <audio>, <video> and <track> elements
  • frame-src: Lists trusted sources for nested browsing contexts loading using elements such as <frame> and <iframe>
  • font-src: Lists trusted sources for fonts loaded using @font-face
  • connect-src: Limits the URLs which can be loaded using script interfaces. Script interfaces include <a> ping, Fetch, XMLHttpRequest, WebSocket and EventSource
  • worker-src: Lists trusted sources for Worker, SharedWorker, or ServiceWorker scripts
  • base-uri: Limits the URLs which can be used in a document’s <base> element
  • plugin-types: Limits the set of plugins that can be embedded into a document by limiting the types of resources which can be loaded. For example, to allow Flash, specify its mime type: application/x-shockwave-flash in this directive
  • sandbox: Put the resource under a sandbox similar to the <iframe> sandbox attribute.
  • form-action: Limits the URLs which can be used as the target of a form submissions from a given context
  • frame-ancestors: Limits valid parents that may embed a page using <frame>, <iframe>, <object>, , or <applet>
  • report-uri: List URL for the web browser to report the Content Security Policy violation. These violation reports consist of JSON documents sent via an HTTP POST request to the specified URI. Deprecated in CSP 3, but still widely supported
  • report-to: report-uri (mentioned above) has been renamed to report-to and report-uri is deprecated in CSP 3. However, at the time of article writing, not a single browser supports report-to. It is perfectly fine to specify both report-uri and report-to to future-proof CSP
  • block-all-mixed-content: Forbids loading any assets using HTTP when the page is loaded using HTTPS
  • upgrade-insecure-requests: Instructs web browser to treat all of a site’s insecure URLs (those served over HTTP) as though they have been replaced with secure URLs (those served over HTTPS). This directive is intended for web sites with large numbers of insecure legacy URLs that need to be rewritten.
  • require-sri-for: Requires the use of Subresource Integrity (SRI) for external scripts or styles on the page

CSP Values

Each directive follows by one or more values separated by whitespace. The acceptable value types are in two main categories: keywords and URI.

All keyword, except wildcard, must be enclosed in single quotes:

  • self’: Restrict source to same origin
  • none’: No source is allowed
  • *: wildcard
  • unsafe-inline’: Allows the inline JavaScript code and stylesheet
  • unsafe-eval’: Allows dynamic JavaScript through eval()

One very common reason to specify external trusted source URI other than the same origin is the need to support loading resource from a Content Delivery Network (CDN), a geographically distributed network of proxy servers that store commonly downloaded content.

URIs must not be enclosed in single quotes!

In CSP 1, only the scheme (http or https), domain and port number are allowed in the URI.

https://example.com:80/

Whereas in CSP 2, subdomains and paths are allowed. This URI allows all files in the js folder:

https://example.com:80/js/

This URI treats js as file, not a folder, as it is not ended with a forward slash:

https://example.com:80/js

To allow all subdomains, use an asterisk as a wildcard.

https://*. example.com:80/

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>


function displayTime()
{
    var d = new Date();
    var n = d.toLocaleTimeString();
    document.getElementById('time').innerHTML = n;
    setTimeout(function () {
            displayTime()
        }, 500);
}
displayTime();


</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';">


// code remains unchanged, so it is not shown.
<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.

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


// code remains unchanged, so it is not shown.

The same concept works for style section.

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

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

https://mysite.com/example.js
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.

 

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this:
search previous next tag category expand menu location phone mail time cart zoom edit close