Scott Smith

Blog Tutorials Projects Speaking RSS

Protect Your Node App's Noggin With Helmet

There are many ways in which attackers will attempt to exploit your applications and users. Some of the more famous are cross-site scripting (XSS), script injection, clickjacking, insecure requests, and identifying web application frameworks to name a few.

In my previous article, 4 Simple Steps to Secure Your Express Node Application, we learned how to use HTTPS to avoid man in the middle attacks, secure our cookies from being read by client side JavaScript, prevent our cookies from being sent on an HTTP request, and how to prevent cross-site request forgery attacks.

This article will focus more on middleware we can add to Express to further lock down our application.

Helmet

We will be using Helmet, a set of Express middleware, to help lock down and secure our web applications.

To install Helmet, simply run the following command in your application.

1
npm install helmet --save

Here is a simple sample Express application to get us started using Helmet. You will notice the main addition is requiring the Helmet module.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Load required modules
var express = require('express');
var helmet = require('helmet');

// Create our Express application
var app = express();

// Simple endpoint
app.get('/', function(req, res) {
  res.send('Time to secure your application...');
});

// Start the server
app.listen(3000);

The following sections will dive into each of the security features provided by Helmet.

Content Security Policy (CSP)

Content Security Policy (CSP) is a promising defense against the risk and impact of XSS attacks. Its main goal is to prevent anything unintended being injected into our page. This can include frames, images, tracking scripts, and opening the door to XSS vulnerabilities.

CSP works by setting whitelists in the Content-Security-Policy response header to define sources of trusted content. The browser is then only allowed to execute or render resources from those trusted sources. This means that if someone were to successfully inject their script into your page, the browser would not execute it as the source would not be in the whitelist.

The main types of resources that can be controlled with CSP are:

  • JavaScript
  • Stylesheets
  • Images
  • AJAX, WebSockets, etc.
  • Fonts
  • Plugins
  • HTML5 Media Elements
  • Frames

So enough about what CSP is and what it provides. You are here to learn how to protect your web application. If you want to learn more, I would suggest a great article on HTML5 Rocks. Let’s dive into how we can implement CSP with Helmet.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Load required modules
var express = require('express');
var helmet = require('helmet');

// Create our Express application
var app = express();

// Implement CSP with Helmet
app.use(helmet.csp({
  defaultSrc: ["'self'"],
  scriptSrc: ['*.google-analytics.com'],
  styleSrc: ["'unsafe-inline'"],
  imgSrc: ['*.google-analytics.com'],
  connectSrc: ["'none'"],
  fontSrc: [],
  objectSrc: [],
  mediaSrc: [],
  frameSrc: []
}));

// Simple endpoint
app.get('/', function(req, res) {
  res.send('Time to secure your application...');
});

// Start the server
app.listen(3000);

So what I have done here is told our Express application to use helmet.csp() and passed in some optional parameters. Helmet does have a way to use it with defaults, but I would suggest being explicit so you know exactly what it is being done. If you do want to use the defaults, you can do it with app.use(helmet());.

defaultSrc

This is the default policy for loading all content. Whatever is defined here applies to all the other type unless you set them to 'none'. In our case we are saying that any content coming from the same origin is allowed.

scriptSrc

This is the policy for controlling the valid sources of JavaScript. In our case we are setting *.google-analytics.com as an allowed source for scripts. This is in addition to the same origin rule we defined in defaultSrc.

styleSrc

This is the policy for controlling the valid sources of stylesheets. What we are doing here is using the 'unsafe-inline' keyword to allow inline stylesheets. If this was not set, any inline stylesheets would not work.

imgSrc

This is the policy for controlling the valid sources of images. Just like scriptSrc, we are setting the allowed domain for images.

connectSrc

This is the policy for controlling the valid sources of AJAX, WebSockets, or EventSource. In this case, we are using the 'none' keyword to state that no content of this type should be allowed. This overrides the defaultSrc directive.

fontSrc

This is the policy for controlling the valid sources of fonts. It is blank to state the only whitelist to use is the one defined in defaultSrc.

objectSrc

This is the policy for controlling the valid sources of plugins like <object>, <embed>, or <applet>. It is blank to state the only whitelist to use is the one defined in defaultSrc.

mediaSrc

This is the policy for controlling the valid sources of HTML5 media types like <audio> or <video>. It is blank to state the only whitelist to use is the one defined in defaultSrc.

frameSrc

This is the policy for controlling the valid sources of frames. It is blank to state the only whitelist to use is the one defined in defaultSrc.

There are four special keywords you can use when defining whitelists:

  1. 'none' will match nothing
  2. 'self' will match the current origin but not subdomains
  3. 'unsafe-inline' allows inline JavaScript and CSS
  4. 'unsafe-eval' allows things like eval() to work

There are also 5 other directives you can use. If interested, you can read more about them here.

  1. sandbox
  2. eportUri
  3. reportOnly
  4. setAllHeaders
  5. safari5

And finally here is what the response header looks like for our example (newlines added for better reading).

1
2
3
4
5
6
7
8
9
10
Content-Security-Policy: 
  default-src 'self';
  script-src *.google-analytics.com;
  object-src ;
  img-src *.google-analytics.com;
  media-src ;
  frame-src ;
  font-src ;
  connect-src 'none';
  style-src 'unsafe-inline'

XSS Filter

The XSS Filter was created to help protect against XSS attack. Ironically, it actually helps attackers perform XSS attacks against older versions of Internet Explorer! You can read more about it here.

What we need to do is have the X-XSS-Protection header set to 0 in order to disable it for older versions of IE. Helmet provides a piece of middleware to do just that. It will detect the web browser and either set it to 1; mode=block or 0.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Load required modules
var express = require('express');
var helmet = require('helmet');

// Create our Express application
var app = express();

// Implement X-XSS-Protection
app.use(helmet.xssFilter());

// Simple endpoint
app.get('/', function(req, res) {
  res.send('Time to secure your application...');
});

// Start the server
app.listen(3000);

Here is what the response header looks like when using a modern web browser without the exploit.

1
X-XSS-Protection: 1; mode=block

And here is what it would look like on an older version of Internet Explorer with the exploit.

1
X-XSS-Protection: 0

Frame Options

To help mitigate the risk of clickjacking attacks, Helmet offers a piece of middleware to help control the X-Frame response header. What this does is allow you to control if and where your page can be put into a <frame> or <iframe>. This is also nice even from a non security point of view. You may not want your site to be loaded within anyone else’s frames from a purely aesthetic point of view.

There are three options with the X-Frame response header:

  1. Deny - Does not allow your page to be served inside any frames.
  2. SameOrigin - Allows your page to be served inside a frame with the same origin.
  3. Allow-From - Allows your page to be served inside a frame from a specific URL.

Deny

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Load required modules
var express = require('express');
var helmet = require('helmet');

// Create our Express application
var app = express();

// Implement X-Frame: Deny
app.use(helmet.xframe());
//app.use(helmet.xframe('deny')); //Same thing

// Simple endpoint
app.get('/', function(req, res) {
  res.send('Time to secure your application...');
});

// Start the server
app.listen(3000);

Here are the response headers.

1
X-Frame-Options: DENY

SameOrigin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Load required modules
var express = require('express');
var helmet = require('helmet');

// Create our Express application
var app = express();

// Implement X-Frame: SameOrigin
app.use(helmet.xframe('sameorigin'));

// Simple endpoint
app.get('/', function(req, res) {
  res.send('Time to secure your application...');
});

// Start the server
app.listen(3000);

Here are the response headers.

1
X-Frame-Options: SAMEORIGIN

Allow-From

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Load required modules
var express = require('express');
var helmet = require('helmet');

// Create our Express application
var app = express();

// Implement X-Frame: Allow-From
app.use(helmet.xframe('allow-from', 'http://example.com'));

// Simple endpoint
app.get('/', function(req, res) {
  res.send('Time to secure your application...');
});

// Start the server
app.listen(3000);

Here are the response headers.

1
X-Frame-Options: ALLOW-FROM http://example.com

HTTP Strict Transport Security (HSTS)

HTTP Strict Transport Security (HSTS) allows a web server to tell user agents to interact with it in the future only over HTTPS. It controls it by defining a period of time with the Strict-Transport-Security response header. This is great if you are running an HTTPS site but still have an HTTP endpoint in order to provide redirection to the HTTPS endpoint. This can help reduce the chance of Man-In-The-Middle (MITM) attacks by reducing the frequency of requests being made over insecure channels.

Helmet provides middleware to help control setting and configuring the HSTS headers. You can specify how long the user agent should make requests over HTTPS and whether or not to include subdomains.

Here it is in action. We are telling it to set the maximum age to 90 days (value is in milliseconds). While the code uses milliseconds, the response header will be in seconds.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Load required modules
var express = require('express');
var helmet = require('helmet');

// Create our Express application
var app = express();

// Implement Strict-Transport-Security
app.use(helmet.hsts({
  maxAge: 7776000000,
  includeSubdomains: true
}));

// Simple endpoint
app.get('/', function(req, res) {
  res.send('Time to secure your application...');
});

// Start the server
app.listen(3000);

Here are the response headers.

1
Strict-Transport-Security: max-age=7776000; includeSubDomains

You should be aware that this only works if your site is running over HTTPS. The middleware will check to see if req.secure is set to true which is automatically populate by Express.

Hide X-Powered-By

By default, Express will add the X-Powered-By header to each response. The actual response header will look like this:

1
X-Powered-By: Express

While by itself it doesn’t cause any security holes, it does give potential attacker useful information. With this information they can focus their attack to exploit known vulnerabilities in Express and Node.

Helmet provides middleware to either strip out the X-Powered-By header or set it to anything else you want.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Load required modules
var express = require('express');
var helmet = require('helmet');

// Create our Express application
var app = express();

// Hide X-Powered-By
app.use(helmet.hidePoweredBy());

// Simple endpoint
app.get('/', function(req, res) {
  res.send('Time to secure your application...');
});

// Start the server
app.listen(3000);

If you don’t want to remove the header, you can set it to anything else using.

1
app.use(helmet.hidePoweredBy({ setTo: 'all your base are belong to us' }));

Which results in the following response header.

1
X-Powered-By: all your base are belong to us

Other

There are a few more things Helmet provides. The documentation on the GitHub repository does a good job of explaining them. If you want to learn more, I would suggest checking each of them out.

  1. IE, Restrict Untrusted HTML
  2. Don’t Infer The MIME Type
  3. Turn Off Caching (I would highly advise against this. Caching is very good for web performance.)
  4. Restrictive Crossdomain.xml

Wrap Up

Helmet is an amazing piece of Express middleware that can help you easily lock down your web application. I would suggest you take the time to better understand the types of attacks and fixes talked about so you can better secure your applications.

I have a lot more tutorials coming so be sure to subscribe to my RSS feed or follow me on Twitter. Also, if there are certain topics you would like me to write on, feel free to leave comments and let me know.