Content Security Policy for Humans
By: Wandrille Verlut
Tags: Security, Web Development, Javascript Development, Content Security Policy, Third Party Integrations

Photo by Scott Webb

Let’s say you organize a party (in a post-COVID world). You prepare the place, buy some tacos and beers and send invitations. You are ready.

Inevitably, some of the most polite guests reach out and ask if they can bring just a few more friends to your house…

At first you’re ok with it: the more the merrier. But then you realise you don’t really know your friends’ friends nor do you trust them. You start to fear they will simply kill the vibe or worse, they might break things.

Welcome to the wonderful world of Content Security Policies. Take the above scenario, but now say you’re creating a cool website. You start with your own content but then, to make it even cooler, you end up with content from other sources (such as pictures or videos). You even rely on external JS to add neat third-party features (analytics tools, social network integrations, ads, or even a chat via iAdvize)… Little do you know that these features may also rely on other pieces of content that you don’t know or eventually trust! Is this party safe? Is the hired DJ trustworthy?

And over the loud party music, a little voice in your head whispers: “Have you heard about XSS vulnerability?”

XSS

Before we continue this story, we need to explain a bit more about XSS. It stands for cross-site-scripting (shouldn’t it be ‘CSS’ ? Oh, wait…)

It means that through one way or another (playing with query Parameters, injecting malicious data in the website database, exploiting existing forms vulnerabilities…), an attacker can execute malicious code inside the victims’ browser when a page is displayed.

This code may display unwanted content (defacing), impersonate users, steal their credentials & information, etc… It is an obviously major security flaw (like hiring an untrustworthy DJ).

How does it relate to our content / party problem?

We’ve seen that XSS attacks rely on loading and executing unwanted code on a website. So if the host is able to tell the browser in a reliable manner what content should be trusted to load and execute, it would make it harder for attackers to exploit such a vulnerability or at the least limit the consequences of a successful execution.

That is exactly what a Content Security Policy (CSP) is all about. It defines a clear set of rules about bringing along additional content. It tells us which guests are actually welcome and it puts a lock on the liquors cabinet.

How does CSP work?

When accessing a webpage, the host will return in the HTTP response an additional ‘ Content-Security-Policy ‘ header with a set of directives.

Content-Security-Policy: default-src https:; report-uri /csp-violation-report-endpoint/

(You may find a complete list of directives there: https://developer.mozilla.org/fr/docs/Web/HTTP/CSP )

Then it is the responsibility of the browser to apply these directives and allow or forbid the content to execute. What happens when some parts of the page break the rule? The browser will simply stop the scripts from executing and report it to the url provided in the header ( in the example above /csp-violation-report-endpoint/ ).

How does it handle third party tags?

With the introduction of CSP, third party tags must communicate explicitly what kind of operation they need to be able to perform, what content they need to load, display or execute.

Then the website may add these rules to its own content security policy.It’s more transparent and secure for the web host.

Let’s take a real world example.

iAdvize is a conversational SAAS solution. It relies on a tag that will load additional content when needed and execute it to then display a notification or a chatbox.

Let’s take a look at iAdvize’s requirements on Content security policy and add subtitles on them for clarity’s sake:

Content-Security-Policy: // allow HTTPS requests and secured web sockets towards iadvize.com connect-src https://*.iadvize.com wss://*.iadvize.com; // create and display iFrames with source being iadvize frame-src https://*.iadvize.com; // display images hosted on iadvize.com img-src https://*.iadvize.com; // load and execute additional scripts from iadvize.com script-src ‘unsafe-inline’ https://*.iadvize.com; // load and execute additional style from iadvize.com style-src ‘unsafe-inline’ https://*.iadvize.com;

Customers wishing to use iAdvize’s services can extend their CSP with these rules, they create a bridge of trust between our solution and theirs.

Wait … “unsafe-inline”?

This ‘unsafe-inline’ attribute in the example above doesn’t seem really… safe, does it? What are inline scripts anyway?

An inline script is directly embedded inside the HTML, unlike external scripts.



<script src=” // external script http://example.com/script.js</a> "> // inline script

<script> console.log(“I’m executing inline”); </script>

It works the same way for styles: you can either load a stylesheet inside a style tag, or write your rules inline through the style tag or attribute.

// external stylesheet <link rel=”stylesheet” type=”text/css” href=”style.css” /> // inline style inside a style tag <style> .h1 { color:blue; } </style> // inline style, inside an HTML attribute <h1 style=”color:blue;padding:30px;”>CSP rocks</h1>

So far, we’ve seen that we could explicitly state our authorized third-party content and code. But if the attacker can inject a script at runtime, which is easily done through online scripting, the browser will have no mechanism to identify if it’s a legitimate one or not. So it is deemed unsafe and default CSP configurations will block it. Instead of using inline scripts, it is best to move inline code in a dedicated file and load it properly.

There are some legitimate cases though when you want to allow third-party code to execute inline scripts and style. iAdvize’s tag for example is an inline snippet that loads the up to date `iadvize.js` code and executes it . It also needs to use inline styles to dynamically position the chatbox on the page and handle its various states of display. In this case inline scripting is necessary to build a dynamic, high-quality experience.

Nonce & Hash

So you can’t completely forbid inline scripts and styles. It still does not mean you want to trust wholeheartedly any script that comes from an outside domain. What you can do however is grant a “digital passeport” to the said inline script so it can load and execute.

If we go back to the party analogy, think of it as having a bouncer at the door verifying that all the people coming in in the middle of the night are either legitimate guests or have been approved and vouched by someone you trust.

In a more practical way, you generate a random number for every page request. It will be valid once and only your vetted, server-generated inline script will know it. External injection becomes much harder, they would need to imitate the same exact random number.

Example

// in you CSP header: Content-Security-Policy: script-src ‘nonce-2726c7f26c’ // in the DOM: <script nonce=”2726c7f26c”> console.log(“I will execute”); </script>

Another option is to provide a hash of the said script:

Content-Security-Policy: script-src ‘sha256-B2yPHKaXnvFWtRChIbabYmUBFZdVfKKXHbWtWidDVF8=’

This too acts as a digital signature ensuring that only trusted code can be executed.

Finally, you may really trust your friends and want to allow them to bring more friends.

Adding the ‘strict-dynamic’ option to the directive above will allow the script with the nonce to load & execute more inline scripts .

// in the header Content-Security-Policy: script-src strict-dynamic ‘nonce-2726c7f26c’; // in the DOM

<script nonce=”2726c7f26c”>

var s = document.createElement(‘script’); s.src = “https://cdn.example.com/some-additional-feature.min.js"; document.body.appendChild(s); </script>

This is especially useful if you want to conditionally load content and features (and save bandwidth).

What did we learn ?

CSP is a great tool, it monitors and controls the code and content executed in the visitor’s browser. It helps mitigate cross-scripting attacks (it does not however fix the XSS vulnerabilities in your code).

It helped us bring along a set of best practices to improve security, among other things, around our inline scripting.

CSP covers a wider range of directives than what we have shown here. You can discover them and evaluate your own Content Security Policy with this tool: https://github.com/google/csp-evaluator. In the meantime, party safely.