Saptak's Blog Posts

Adding CSP hashes for styles in Chromium

Posted: 2021-01-17T14:13:55+05:30

Content Security Policy (or CSP) is a way of avoiding certain types of website-related attacks like cross-site scripting and malicious data injections. It is a way by which website developers can tell the browser what content origins are approved so that everything else is blocked. One needs to add a Content-Security-Policy HTTP header mentioning the sources which they allow for loading scripts, styles, images, etc.

To read in detail about CSP, check Content Security Policy Level 3 working draft.

We are going to discuss here why sha256 hashes often don't let inline styles to not pass in chromium browsers. Chromium browser console complains about the style-src hashes mismatch even though it shows them to be the same. Why? And how to solve it?

TL;DR: If using <style>, use style-src. If using style="" attribute in HTML tag, use style-src-attr

Now, if you are interested in more information, let's dive a little deeper into what's going on.

Hashes to allow inline styles & scripts

The usual practice of having a tight, secure CSP is to not allow any inline style or inline scripts. This helps mitigate malicious scripts entered via data injection from getting executed.

When I say inline scripts, one might understand 2 different scenarios:


<!-- Scenario 1 -->
<script>alert('Hello world');</script>

or


<!-- Scenario 2 -->
<button onclick="alert('Hello world');">
    Click me!
</button>

Now, the easiest way to allow this would be to add unsafe-inline in script-src of the CSP. But then we are back to the problem of executing malicious scripts entered by data injection. There are two ways to still allow only these scripts to work: nonce and sha256 hashes. We are going to talk about sha256 hashes here.

The idea is to get the sha256 hash of the entire script and add it to the script-src. So in this case, it would be something like this:

script-src 'self' 'sha256-DUTqIDSUj1HagrQbSjhJtiykfXxVQ74BanobipgodCo='

You can get the hash from https://report-uri.com/home/hash. Also, chromium browsers will usually show the hash that should be added for a particular inline script.

The Problem

Now, all this sounds good, and in Firefox, just adding the above to your CSP will make both the scripts to work. However, in chromium, the above CSP will work only in Scenario 1 but not in Scenario 2. You can read more about the discussion here: https://bugs.chromium.org/p/chromium/issues/detail?id=546106#c8

In JavaScript, I think in general scenario 1 will be much more encouraged than scenario 2. So scenario 2 might not be encountered that often. However, the situation changes, when it comes to styles (or CSS)

In case of inline styles, following are the scenarios:


<!-- scenario 1 -->
<style>p{color: blue;}</style>

and


<!-- scenario 2 -->
<p style="color: blue;">This is a text</p>

In CSS, the second scenario is much more common when someone does inline styles than scenario 1. But again, in this case, adding a sha256 hash to style-src won't execute the scenario 2 in chromium browsers.

This is because styles added in scenario 2 are part of the style attribute in the HTML tag which in CSP terms are essentially event handlers. According to w3c CSP draft, the hash in style-src allows the inline styles mentioned inside <style> tag to pass but doesn't allow event handlers (as is the case in scenario 2). There's more on this discussion here.

So it's a feature?

Yes, it is a feature. In chromium browsers, adding a hash to style-src only allows any inline style written inside the <style> tags to execute. This is by design. If you need to execute the inline styles present in style= attribute of HTML tags, you need to use another directive in CSP called style-src-attr. Similarly, script-src-attr should be used if you are doing JavaScript event handling in the HTML tag itself.

So, for example, if you want to only allow an inline CSS such as this:


<p style="color: blue;">This is a text</p>

all you need to do is put the sha256 hash in style-src-attr along with 'unsafe-hashes'. This will tell the browser to allow any inline style, with the hashes that you added in style-src-attr to be executed.

So the CSP will have something like this:

style-src-attr 'unsafe-hashes' 'sha256-C8uD/9cXZAvqgnwxgdb67jgkSDq7f8xjP8F6lhY1Gtk='

And, that's it! That will do the trick in any chromium browser. The related code for chromium can be found here. According to caniuse.com, all chromium browsers above 75 supports this behaviour.

Even though firefox still doesn't have support for style-src-attr but it allows inline styles and scripts of all types to pass based on style-src and script-src hashes. So as long as the hash is mentioned in both style-src and style-src-attr, it should work in most of the browsers.

As for the explanation behind why 'unsafe-hashes', there is a pretty good explainer document written by andypaicu talking about exactly this.

Also, read more about style-src-attr in detail in the w3c draft to understand exactly what's happening and what kind of risk it may still pose.

PS: Inline JavaScript event handlers using script-src-attr can be very risky given an attacker can trigger a passing javascript from within an unrelated HTML tag.