In our previous post, we discussed the importance of securing your HTTP headers and how Helmet.js can make this easy for apps that use Express.

Helmet.js’s Github page has a wealth of documentation on how to tweak different security header configurations. For this post, we’ll focus on tuning the CSP header. CSP headers can prevent unwanted injection of JavaScript and other files onto your webpage.

What is Content-Security-Policy (CSP)?

CSP instructs the browser how to process certain directives (e.g., code/configurations that instructs the browser to include resources onto the webpage). It was designed to help minimize the impact of attacks that exploit cross-site scripting vulnerabilities. Cross-site scripting is a vulnerability that allows attackers to inject unwanted and/or malicious JavaScript onto a webpage. By default, browsers that do not see a CSP header in an HTTP response will accept all directives, including resources from external domains.

Helmet.js does not add a CSP header as part of it’s default configuration. Because CSP can block the inclusion of files and resources from legitimate third-parties (like CDNs), browsers default to an open and permissive CSP to avoid breaking website functionality or negatively impacting user experience.

Many modern web apps include JavaScript, CSS, fonts, and other resources from different domains. For example, as a developer, you may decide to use a third-party analytics service that requires you to install a small JavaScript file from another domain. Or, you may choose to include Bootstrap files from a CDN. In reality, there are dozens of use cases for including files and resources from external domains.

CSP does not fix cross-site scripting vulnerabilities, but it can help limit the impact of one. As a developer, it’s important to understand how to configure and use CSP as an additional layer of defense-in-depth for your Express app.

CSP Saves the User: An Example

Let’s walk through an example of CSP preventing the exploitation of a cross-site scripting vulnerability.

We have a simple website that allows users to add their name to raffle. It looks like this:

example-website

This site is built on jQuery and Bootstrap, and includes directives for downloading JavaScript, CSS, and font files from CDNs. Here are the CDNs we use:

  • Bootstrap JavaScript, CSS, and font files: maxcdn.bootstrapcdn.com
  • jQuery JavaScript files: code.jquery.com

Unfortunately, our website does not have a CSP header configured, and also has a persistent cross-site scripting vulnerability. The cross-site scripting vulnerability allows any user to submit <script> tags into the text field and have it displayed on the webpage. These <script> tags may contain directives to malicious JavaScript on external domains. Worst of all, this vulnerability affects all users who visit the webpage since it’s persistent in nature - once injected it stays on the page permanently!

As you can see below, evil_script.js is downloaded and run in the user's browser!

permissive-csp

Configuring CSP

If we had CSP enabled, our users would not have be subject to the malicious JavaScript from i-am-an-evil-website.hack. To help our users avoid exploitation, Helmet.js gives us a flexible way to configure the CSP to only accept resources from specific domains.

There are many ways to configure CSP, but here are two options below:

1. Allow resources from your domain only:

app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"]
  }
}));

The CSP header will look like this:
Content-Security-Policy: default-src 'self'

2. Allow resources from your domain only, with an exception for specific CDNs we use and trust:

app.use(helmet.contentSecurityPolicy({
 directives: {
   defaultSrc: ["'self'"],
   scriptSrc: ["'self'", 'code.jquery.com', 'maxcdn.bootstrapcdn.com'],
   styleSrc: ["'self'", 'maxcdn.bootstrapcdn.com'],
   fontSrc: ["'self'", 'maxcdn.bootstrapcdn.com']
 }
}));

The CSP header will look like this:
Content-Security-Policy: default-src 'self'; script-src 'self' code.jquery.com maxcdn.bootstrapcdn.com; style-src 'self' maxcdn.bootstrapcdn.com; font-src 'self' maxcdn.bootstrapcdn.com

The second option works best for us, since we’ve chosen to include JavaScript, CSS, and font resources from a CDN. You'll also notice that we are using four different directives in our Helmet.js code:

  • defaultSrc: specifies the default whitelist for loading content such as JavaScript, images, CSS, fonts, etc. The self directive permits loading from the same origin.
  • scriptSrc, styleSrc, fontSrc: specifies a whitelist of valid sources/domains for loading scripts, CSS, and fonts, specifically.

A full list of CSP directives can be found here.

After enabling CSP, evil_script.js is blocked but Bootstrap and jQuery files from the CDNs are permitted.

csp-blocks-evil-script

Next Steps

As stated before, Helmet.js’s documentation does a thorough job detailing the different CSP configuration options. There are many other ways to configure your CSP head, so check out this page.

As you can see, HTTP headers are an important part of defending your web app from attackers -- and Helmet.js makes it easy! However, securing your headers isn’t the only thing you need to do. In our example, we still need to fix the underlying Node.js code that created the persistent cross-site scripting vulnerability. Solutions like Hailstone are excellent at finding security bugs in the Node.js code you write and use.

Stay tuned for future posts about other ways you can keep your web app secure!

Want a demo of Hailstone?

Hailstone is currently in early access and we're seeking Design Partners to provide us with feedback. If you're interested in joining, or seeing a demo, sign up for our mailing list - we'll be in touch shortly!