How to prevent the use of unsafe-inline in CSP

It is best to prevent the usage of "unsafe-inline" in your Content Security Policy (CSP) header. In this article, I will explain why that is the case and how to transform unsafe assets into safe ones.

CSP primer

First of all, any content security policy is better than no policy at all. So if you don't know what CSP is, or if you don't use it in your projects, it is time to read up:

Why is unsafe-inline dangerous?

It is not uncommon to allow 'unsafe-inline' in a CSP policy. In fact, it was part of this blog's policy until earlier this day.

So, why is this a bad thing?

The beauty of the CSP header is that it can protect websites against cross-site scripting (XSS). When properly configured, modern browsers will block any script or style that is not explicitly whitelisted.

The use of 'unsafe-inline' basically allows unknown scripts and styles to be executed and thereby weakens the whole system.

Cross-site scripting vulnerability

Suppose an attacker has found a way to store HTML code on your website. This means the attacker can now place <script>, <style> and other HTML content.

A basic CSP header will protect against the most critical attacks. The attacker can not make an ajax request to his own server because the domain will not be whitelisted. The same holds for loading images or framing content. This is why it is important to never whitelist "https://" but always list all domains you need.

But, the attacker can still alter the behavior of the page by executing an inline script. Or he can completely change the looks of the page by inlining styles.

This is what we can prevent by not allowing 'unsafe-inline'.

Luckily, there are still ways to use inline scripts and styles on a page. We just have to "convert" them van unsafe to safe by using "nonces" or "hashes".

Using nonces to make inline content safe

A nonce is a random string that is unique for each request. It is part included in the CSP header like so:

script-src: 'nonce-a9d09b55f2b66e00f4d27f8b453003e6';
style-src: 'nonce-a9d09b55f2b66e00f4d27f8b453003e6';

We can now create safe inlined content like this:

<script nonce="a9d09b55f2b66e00f4d27f8b453003e6">...</script>
<style nonce="a9d09b55f2b66e00f4d27f8b453003e6">...</style>

Using hashes to make inline content safe

The other way to whitelist inline content is by computing a hash of the content and providing that in the CSP header.

Use PHP to compute the hash of the content like so:

base64_encode(hash("sha256", "this-is-the-content", true));

Then include the hash in the policy like so:

script-src: 'sha256-cBbpOAakpiPtmHk5QmIjJYt5WwYh3a1a0P1LpPh1AjQ=';
style-src: 'sha256-cBbpOAakpiPtmHk5QmIjJYt5WwYh3a1a0P1LpPh1AjQ=';

Make sure you hash the exact content of your script or styles tag, including any spaces or newlines. You can include as many hashes as you want.

Caveats

Although above methods are pretty simple in theory, I've found it to be hard to implement them. Some problems I've come upon in recent projects:

  • Laravel debugbar injects its content into the response body automatically. The content is unique for each request and creates an inline style tag on its own.
  • Many services that you have to embed (for example AddThis widgets) try to create inline tags.
  • I wasn't able to get this working in combination with a Vue app.

My best advice would be to start every project with a very strict policy. It is much harder to apply this to existing projects than to new ones.

Once inline content is blocked, you can try to whitelist it with the above methods. If this doesn't work you basically have 2 options: remove the content or choose to allow unsafe-inline content.

If you know a method to get Vue.js apps working without allowing "unsafe-inline", please let me know :)

Further reading

Comments (7)

Got a question? Liked the article or got a suggestion? Leave a comment to let us know.

It should be noted that "unsafe-inline" unfortunately still has to be supported for situations where you have to support use on older browsers. If you are using nonces in your script and style tags, you can still have unsafe-inline in your script-src and style-src, and then when modern browsers (any browser except Internet Explorer) load the CSP definitions, they pick up the nonce and automatically ignore the "unsafe-inline" portion of your script-src or style-src definitions. Then if you load the page in an older browser, the "unsafe-inline" portion can be relied upon, since older browsers (like Internet Explorer) don't support nonces in HTML markup. The downside to this is older browsers will still utilize your site in an unsecure manner, but the alternative is older browsers not being able to run at all on your site.
Thanks for the useful information, Pat!
I have a query on this , I am doing audit of ine of the Web application in that it is using CSP with unsafe inline and unsafe eval
Website is in .Net and using javascript as well.

Client don't want to use it with unsafe inline and unsafe eval.

Is there any substitute of this so I can suggest them.
Any suggestions??
Hi Arpit Jain , Is your Query solved ? This is exactly what i am facing now , since its legacy application we cannot change all inline scripts.
Hi Geke, you will need to either use hashes or nonces (or not inline the scripts). If this is not possible for you, then you cannot prevent using unsafe-inline.
Still looking for away of whytelisting inline styling aka 'style="position absolute; left: 0; etc.." used for positioning elements in a masonry layout, wich is almost impossible to be done with classes.
Can any style atribute be attacked?
Hi Mike,

This is a problem that I don't believe you can fix without allowing inline styles, since the inline styles will be too dynamic. You might try to replace the Javascript masonry solution with one based on new CSS properties. If you google "css masonry layout" that might give you some viable solutions.

Good luck!

Barry