Implementing Content Security Policy

The add-ons team recently completed work to enable Content Security Policy (CSP) on addons.mozilla.org (AMO). This article is intended to cover the basics of implementing CSP, as well as highlighting some of the issues that we ran into implementing CSP on AMO.

What is Content Security Policy?

Content Security Policy (CSP) is a security standard introduced to help prevent cross-site scripting (XSS) and other content injection attacks. It achieves this by restricting the sources of content loaded by the user agent to those only allowed by the site operator.

The policy is implemented via headers that are sent with the server response. From there, it’s up to supporting user agents to take that policy and actively block policy violations as they are detected.

Why is it needed?

CSP is another layer of defense to help protect users from a variety of attack vectors such as XSS and other forms of content injection attacks. While it’s not a silver bullet, it does help make it considerably more difficult for an attacker to inject content and exfiltrate data.

Building websites securely is difficult. Even if you know general web security best practices it’s still incredibly easy to overlook something or unwittingly introduce a security hole in an otherwise secure site.

CSP works by restricting the origins that active and passive content can be loaded from. It can additionally restrict certain aspects of active content such as the execution of inline JavaScript, and the use of eval().

Implementing CSP

To implement CSP, you must define lists of allowed origins for the all of the types of resources that your site utilizes. For example, if you have a simple site that needs to load scripts, stylesheets, and images hosted locally, as well as from the jQuery library from their CDN, you could go with:

Content-Security-Policy:<br>    <b>default-src</b> 'self';<br>    <b>script-src</b> 'self' https://code.jquery.com;

In the example above, Content-Security-Policy is the HTTP header. You can also specify Content-Security-Policy-Report-Only, which means that the user agent will report errors but not actively block anything. While you’re testing a new policy, this is a useful feature to enable.

For script-src, we have to also explicitly list 'self' because if you define a directive then it no longer inherits from default-src.

It’s very important to always define default-src. Otherwise, the directives will default to allowing all resources. Because we have default-src 'self', this means that images served from the site’s domain will also be allowed.

default-src is a special directive that source directives will fall back to if they aren’t configured. However, the following directives don’t inherit from default-src, so be aware of this and remember that not setting them to anything means they will either be unset or use the browser’s default settings:

  • base-uri
  • form-action
  • frame-ancestors
  • plugin-types
  • report-uri
  • sandbox

Setting 'self' as default-src is generally safe, because you control your own domain. However if you really want to default to locking things down more tightly you could use default-src 'none' and explicitly list all known resource types. Given the example above, this would result in a policy that looks like:

Content-Security-Policy:<br>    <b>default-src</b> 'none';<br>    <b>img-src</b> 'self';<br>    <b>script-src</b> 'self' https://code.jquery.com;<br>    <b>style-src</b> 'self';
If you rely on prefetching, you might encounter problems with default-src 'none'. On AMO, we discovered that browser prefetching in Firefox will not be identified as a specific content type, therefore falling back to default-src. If default-src doesn’t cover the origin involved, the prefetched resource will be blocked. There’s a bug open with additional information on this issue.

Dealing with inline script

CSP by default doesn’t allow inline JavaScript unless you explicitly allow it. This means that you need to remove the following:

  • <script> blocks in the page
  • DOM event handlers in HTML e.g: onclick
  • javascript: pseudo protocol.

If you do need to allow it then CSP provides a way to do it safely through the use of the nonce-source or hash-source directives, which allow specific blocks of content to be executed. You can opt out of this protection through the use of ‘unsafe-inline’ in the script-src directive, but this is strongly discouraged as it opens up your site to XSS attacks.

For additional information on nonce-source and hash-source, see CSP for the web we have.

Dealing with eval()

CSP also blocks dynamic script execution such as:

  • eval()
  • A string used as the first argument to setTimeout / setInterval
  • new Function() constructor

If you need this enabled you can use 'unsafe-eval' but again this is not recommended as it is easy for untrusted code to sneak into eval blocks.

On AMO, we found a lot of library code that used eval and new Function, and this was the part of CSP implementation that took the most time to fix. For example, we had underscore templates that used new Function. Fixing these required us to move to pre-compiled templates.

Dealing with cascading stylesheets (CSS)

CSP defaults to not allowing:

  • <style> blocks
  • style attributes in HTML

This was a bit more of a problem for us. Lots of libraries use style attributes in HTML snippets added to the page with JavaScript and we had a sprinkling of style attributes directly in HTML templates.

It’s worth mentioning that if style properties are updated via JavaScript directly, then you won’t have a problem. For example, jQuery’s css() method is fine because it updates style properties directly under the covers. However, you can’t use style="background: red" in a block of HTML added by JS.

This can be a bit confusing because in the Firefox inspector, style properties that have been added via JavaScript look identical to style attributes in HTML.

As before, you can use the nonce-source and hash-source directives if you need a controlled approach to allow select pieces of inline CSS.

You’re probably thinking, “This is CSS, what’s the risk?” There are various clever ways that CSS can be used to exfiltrate data from a site. For example, with attribute selectors and background images, you can brute force and exfiltrate attribute sensitive data such as CSRF tokens. For more information on this and other more advanced attack vectors through CSS see XSS (No, the _other ‘S’).

Using 'unsafe-inline' for style-src is not recommended, but it’s a case of balancing the risks against the number of changes that would be necessary to eliminate inline styles.

Reporting

It’s a good idea to set the report-uri directive and point it somewhere to collect JSON reports of CSP violations. As CSP doesn’t currently coalesce error reports, a single page with multiple errors will result in multiple reports to your reporting endpoint. If you run a site with a large audience, that endpoint can receive a significant amount of traffic.

In addition to reports triggered by actual violations, you’ll also find that many add-ons and browser extensions can cause CSP violations. The net result is a lot of noise: Having something that allows for filtering on the incoming data is highly recommended.

Testing

Once you have created your initial policy, the next step is to test it and fix any missing origins. If you run a large site, you may be surprised by the number of sources that you are pulling resources from. Running the site with CSP in report-only mode allows you to catch problems via the console and CSP reports before they actively block things.

Once everyone has confirmed that there’s nothing being blocked erroneously, it’s time to enforce the policy. From there on, it’s just a case of watching out for anything that was missed and keeping the policy up to date with browser support for some of the newer features in CSP.

Implementing

After you have settled upon a policy that works properly, your next step is to configure your system to deliver the CSP directives. Implementing this varies widely depending on your choice of web server software, but it should generally look like this:

# Enable CSP in Apache
Header set Content-Security-Policy "default-src 'none'; img-src 'self';
    script-src 'self' https://code.jquery.com; style-src 'self'"
# Enable CSP in nginx
add_header Content-Security-Policy "default-src 'none'; img-src 'self';
    script-src 'self' https://code.jquery.com; style-src 'self'";

If your service provider doesn’t offer control over your web server’s configuration, don’t panic! You can still enable CSP through the use of meta tags. Simply have your meta tag be the first tag inside <head>:

<!-- Enable CSP inside the page's HTML -->
<head>
    <meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self';
          script-src 'self' https://code.jquery.com; style-src 'self'">
</head>

Our final implementation

Given that AMO is an older and extremely complex site, you’re probably curious as to what our final policy ended up looking like:

Content-Security-Policy:<br>    <b>default-src</b> 'self';<br>    <b>connect-src</b> 'self' https://sentry.prod.mozaws.net;<br>    <b>font-src</b> 'self' https://addons.cdn.mozilla.net;<br>    <b>frame-src</b> 'self' https://ic.paypal.com https://paypal.com<br>        https://www.google.com/recaptcha/ https://www.paypal.com;<br>    <b>img-src</b> 'self' data: blob: https://www.paypal.com https://ssl.google-analytics.com<br>        https://addons.cdn.mozilla.net https://static.addons.mozilla.net<br>        https://ssl.gstatic.com/ https://sentry.prod.mozaws.net;<br>    <b>media-src</b> https://videos.cdn.mozilla.net;<br>    <b>object-src</b> 'none';<br>    <b>script-src</b> 'self' https://addons.mozilla.org<br>        https://www.paypalobjects.com https://apis.google.com<br>        https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/<br>        https://ssl.google-analytics.com https://addons.cdn.mozilla.net;<br>    <b>style-src</b> 'self' 'unsafe-inline' https://addons.cdn.mozilla.net;<br>    <b>report-uri</b> /__cspreport__

Wow! As you can imagine, quite a lot of testing went into discovering the myriad resources that AMO utilizes.

In Summary

The older your site is, the more work it will take to set and adhere to a reasonable Content Security Policy. However, the time is worth spending as it’s an additional layer of security that supports the idea of defense in depth.

Further Reading

About April King

IRC: April

More articles by April King…

About Stuart Colville

Stuart is the Engineering Manager for Firefox Add-ons.

More articles by Stuart Colville…


11 comments

  1. Scott Helme

    Great article! It’s nice to see organisations leading the way with CSP and showing just how beneficial it can be. I think CSP reporting is also very important but as you mentioned it can be quite noisy, generate a lot of traffic and being able to filter incoming reports is important. I setup a free service to allow sites to collect CSP reports, and filter them, that also presents the data in an easy to use format. You can find it at https://report-uri.io

    Hopefully this will increase the value of CSP deployments by giving site owners a means to collect the valuable data that’s available.

    February 16th, 2016 at 12:17

  2. barretlee

    nice!

    February 16th, 2016 at 16:32

  3. Lucas Rinaldi

    Thanks for your article. CSP is very beneficial to us developers. Nice way to get me started.

    February 18th, 2016 at 05:16

  4. Earl

    Congratulations… on breaking my bookmarklets which did nothing more than scroll the page to the top and to the bottom. Kudos!

    February 18th, 2016 at 07:56

  5. April King

    @Earl: There is a bugzilla bug about this issue, if you’d care to comment or be emailed on updates to it:

    https://bugzilla.mozilla.org/show_bug.cgi?id=866522

    February 18th, 2016 at 08:32

  6. Koemsie Ly

    Thanks for your nice article. CSP is very useful to developers. Nice to get me started.

    February 23rd, 2016 at 20:57

  7. Seth Berger

    Thanks April and Stuart for this great article, and thanks Scott for your articles and your great web service for testing sites! I have embarked on implementing a CSP for my own service, OurEvents.

    Do any of you know how to allow img-src data: when explicitly specified by the site’s css files (as permitted by style-src) but to block data: images otherwise?

    If you look at the jQuery Mobile css file, you’ll see a zillion icons in data:image/svg+xml format. Of course, I could block inlineSVG and use their png icons, but then loading each icon requires another request to the web server.

    Ideally there would be an img-data-src:’sitecss’ directive that would permit the browser to use any data: images found in the explicitly specified css files. Without this I do not know how to allow some data: images but block others.

    February 26th, 2016 at 08:19

  8. Dan Veditz

    Seth: There is no specified way in CSP to distinguish HTML image tags from DOM-injected images from CSS-specified images. You could try to spark interest on the public-webappsec mailing list (w3.org) but I suspect most people will find the use-case too narrow to justify the extra complication. Allowing data: for images isn’t all that dangerous: they can at worst deface your site if you have an injection that allows thousands of bytes. No scripts will run, and it won’t be displayed if it’s not valid image data.

    February 26th, 2016 at 09:37

  9. Seth Berger

    Dan: Unfortunately many of the popular browsers have had image rendering vulnerabilities during the past couple of years. I wouldn’t think adding the string img-data-src:’sitecss’ would add much complication.

    Another question for anyone: Like images, browsers have also had vulnerabilities with fonts and so I host font-awesome locally for security and to prevent unexpected changes to the site UI. However, I would consider using the BootstrapCDN if I could specify a CSP font-src rule with the font file url, file size, and file hash and also specify my local copy of my font as a backup. Is this possible?

    Of course, anything other than a 200 OK response from the CDN should trigger use of the local copy. For example, if the CDN usually serves over https but later decides to redirect to http, that should trigger use of the local copy served over https.

    February 26th, 2016 at 10:52

  10. Thor Brendan Bunnyson

    Guys, do you have any solution on how to download a Addons by Right-clicking and “Save Link as…”, this completely breaks it, thanks.

    March 14th, 2016 at 22:14

  11. April King

    Thor — I’m not having that problem at all. I can Save Link as… and download the XPI without any issue. Perhaps you have an add-on installed that is interfering with it?

    I would recommend trying with a clean profile and seeing if you still have this issue.

    March 15th, 2016 at 07:00

Comments are closed for this article.