Content Security Policy – Demystified

128
128

Content security policies help web applications defend against cross-site scripting and Clickjacking attacks. It’s a defense-in-depth approach towards preventing client side injection attacks – especially XSS. It is basically a policy that limits the types of resources loaded in an application. The policy is set through an HTTP response header or meta tag, which the browser interprets and applies accordingly. Without caring much about CSP’s terminologies, let’s start with a simple policy.

img-src

The following HTML file successfully loads the logo of yahoo.

<html>
<head>
</head>
<body>
<img src="https://s.yimg.com/rz/p/yahoo_homepage_en-US_s_f_p_bestfit_homepage_2x.png">
</body>
</html>


Now, let’s modify the HTML file to include a simple policy.

<html>
<head>
<meta http-equiv="Content-Security-Policy" content="img-src 'self'">
</head>
<body>
<img src="https://s.yimg.com/rz/p/yahoo_homepage_en-US_s_f_p_bestfit_homepage_2x.png">
</body>
</html>

Now, It throws an error of CSP violations in the console.

The reason for this error is because the policy restricts the loading of images from third-party sites. We can whitelist the sites from where the images can be loaded.

<html>
<head>
	<meta http-equiv="Content-Security-Policy" content="img-src 'self' https://s.yimg.com">
</head>
<body>
<img src="https://s.yimg.com/rz/p/yahoo_homepage_en-US_s_f_p_bestfit_homepage_2x.png">
</body>
</html>

After whitelisting the site “s.yimg.com”, the image loads without any issues. Notice the use of the scheme “https” up there. CSP is very restrictive, and must conform to the following criteria:

  1. Scheme: The browser does not load resources from “http://” if the policy whitelists “https://”.
  2. Port: The browser does not load resources from other ports if the port is 80.
  3. Domain: The browser does not load resources from subdomain.example.com if the whitelisted site is example.com

Now, let’s change the sub-domain from “s.yimg.com” to “t.yimg.com” and see what happens.

<html>
<head>
	<meta http-equiv="Content-Security-Policy" content="img-src 'self' https://t.yimg.com">
</head>
<body>
<img src="https://s.yimg.com/rz/p/yahoo_homepage_en-US_s_f_p_bestfit_homepage_2x.png">
</body>
</html>

We see the following error in the console, meaning that the page has been blocked from being loaded.

Content Security Policy: The page’s settings blocked the loading of a resource at https://s.yimg.com/rz/p/yahoo_homepage_en-US_s_f_p_bestfit_homepage_2x.png (“img-src”).

CSP Terminologies

We have the basics covered. Now, let’s dive into some terminologies.

Directive: Here, “img-src” is the directive. It basically means the resource being loaded.

Source: We see that the image in the example above is being loaded from s.yimg.com. That’s what a source means. It basically means from where the resources are loaded.

Sources can be any of the following:

https://example.com – Strictly loads resources from only example.com with a secure connection
http://example.com – Strictly loads resources from only example.com with an insecure connection
example.com – Loads resources either from a secure or insecure connection
*.example.com – Loads resources from any of the sub-domains of example.com
example.com:8082 – Loads resources from example.com with either secure or insecure connection from the 8082 port only
http: – Loads resources from any site with an insecure connection
https: – Loads resources from any site with a secure connection
‘self’ – Loads resource from the same origin
‘none’ – Disallows loading any resources

There are other sources that we will get back to later.

script-src

We’ve experimented with ‘img-src’. Let’s play with another directive ‘script-src’ which restricts the loading of scripts from third-party sites, or even the same origin, based on various parameters.

<html>
<head>
</head>
<body>
<script src="https://gist.githubusercontent.com/codedbrain/04461defc3cf5f67974c540c55dad168/raw/1ff49987d063aa7aae3eb46868c04ab0ff45d149/alert.js"></script>
</body>
</html>


Running this pops up an alert box because we don’t have CSP referenced. If we use the following policy

script-src ‘self’;

It will disallow loading the script from gist.githubusercontent.com because it does not comply with the source ‘self’ that only allows loading scripts from the same origin. The policy above works if your web application does not load scripts from other sites. If you’re using CDN to store javascript files then you have to whitelist the sources. Say cdn.example.com then the policy would be

script-src ‘self’ cdn.example.com

The policy above allows loading javascript files from both cdn.example.com and the same origin. There are various ways through which we can run Javascript; loading as an external link, Javascript blocks, or inline handlers. Let’s play and see how a simple policy script-src ‘self’ affects the loading of scripts.

<html>
<head>
</head>
<body>
<script>
 console.log("JS block");
</script>
<input type="button" onclick=console.log("Inline")>
</body>
</html>

This successfully logs “JS block” and “Inline” to the console. Now, let’s write the following policy

script-src ‘self’

<html>
<head>
 <meta http-equiv="Content-Security-Policy" content="script-src 'self'">
</head>
<body>
<script>
 console.log("JS block");
</script>
<input type="button" onclick=console.log("Inline")>
</body>
</html>

It throws an error “Content Security Policy: The page’s settings blocked the loading of a resource at inline (“script-src”)“.

It means that JS blocks and inline event handlers don’t work, even with the source ‘self’ or if they are of the same origin. So, if you’re implementing CSP then you should move these to javascript files.

If you don’t want to move the files, there is a way to load these inline handlers and javascript blocks by making use of unsafe-inline.

script-src ‘self’ ‘unsafe-inline’

Now, the inline JS and block JS don’t get blocked by the browser. However, it defeats the whole point of CSP.

Consider an HTTP parameter vulnerable to XSS.

example.com/display?name=codedbrain

This simply prints out the name, which causes cross-site scripting. Although the policy above restricts JS referenced from third-party sites, we can still use inline event handlers or JS blocks to perform the attack.

example.com/display?name=”><script+src=’https://another-site.com/x.js’></script>

This gets blocked because our policy only allows scripts from the same site. Since it does not block inline event handlers, we can use the following to trigger XSS.

example.com/display?name=”><img src=x onerror=alert(1)
>

This problem can be solved by using nonce and hashes. We generate cryptographic nonces and hashes and include them in the scripts. Consider the following policy :

script-src ‘self’ ‘nonce-axaaabbbcccc’

If the attacker tries to load JS with example.com/display?name=”><script>alert(1);</script> then the browser blocks the attempt, as there is no nonce in the script tag and one can’t simply guess the value of the nonce. However, an attacker can make use of an inline event handler to perform the XSS. Using nonce does not prevent against injection of inline event handlers. If your application makes use of inline event handlers then consider switching them to JS blocks and use nonces there.

There are other CSP directives — img-src,frame-src,font-src, etc. — which are beyond the scope of this writeup. I would recommend the readers understand the basics of CSP before moving on to them.

Some Key Elements for CSP Implementation

Implementing CSP requires thorough understanding of how an application works because it frequently requires code refactoring. Once my team took down a production application because of an immature CSP deployment. Some features were down for around 11 hours.

Below, I will describe a few ways you can plan for the adoption of CSP in your production environment.

Refactor all in-line event handlers.

For example, code blocks like,

<div id="cols-1" onclick="perform()"></div>


needs to be changed to something like,

<script nonce="nonces">
 document.getElementById("cols-1").addEventListener("click",function perform(){...});
</script>

Add cryptographically generated nonces to script tags.

Generate nonces server side and put them in every script tag. For example,

<script src="/path/script.js"></script>

should be changed to something like,

<script nonce="nonces" src="/path/script.js"></script>

Refactor parser-inserted Javascript to non-parser-inserted Javascript.

For example, an HTML parser is required to execute JS if it is parser-inserted. Snippets like below,

document.write("<script>alert(1);</script>") 


need to be changed into something like,

var script = document.createElement('script');
script.src = "script.js";
script.nonce = "nonce";
document.head.appendChild(script);

If your application makes use of eval() then it also needs to be changed into something like JSON.parse(). If the use case does not fit then you have to make use of ‘unsafe-eval’, which unfortunately compromises application security. In such cases, you can sanitize the users input before dropping the source into the sink.

Report-Only Mode

This is a very powerful feature of CSP. You can implement report-only mode to read the CSP violations instead of blocking the attempts.

Say your organization has a CSP policy you want to implement. It might break some production applications. If you implement a report-only mode, then your application only receives violation errors but lets the code run through. Then, you can make changes to CSP policy or refactor the code accordingly.

Content-Security-Policy-Report-Only


If your environment involves a dedicated QA team then the QA team can be asked to look into the console for any CSP violations. It can be integrated into the testing phase as a process.

Conclusion

Content security policy is just a defense-in-depth strategy to defend against cross site scripting attacks. It’s not the only thing you should rely to prevent cross site scripting. There are several ways through which CSP can be bypassed. There are instances where CSP does not work and proper sanitization should come into play. Consider the following code

<script nonce="nonce">
  function changeto(id){
      document.getElementById('somevalue').value=id;
  }
</script>

In the example above, user input is directly passed into the function. Here, CSP violation does not occur because it has valid nonce. However, malicious code can still be injected.

So, if your application uses a dynamically generated value passed into <script>, then there should be proper sanitization of user input. CSPs are not supported by all browsers so there should be a proper fallback as well. There are techniques like post XSS or markup injections which can be abused for data exfiltration even though javascript can’t be injected and executed directly. Whilst a CSP-like framework can help mitigate the impacts of an XSS attack or even stop it entirely, it should not be the only measure to rely upon.

Coded Brain

Hi , I am an information security enthusiast from Nepal.

Leave a Reply

Your email address will not be published. Required fields are marked *