2015 October Update: we've published an updated & extended version of this article. Learn more about Node.js security here: Node.js Security Checklist

Node.js is getting more and more mature, no doubt - despite this, not a lot of security guidelines are out there.

In this post I will share some points you should keep in mind when it comes to Node.js security.

No eval, or friends

Eval is not the only one you should avoid - in the background each one of the following expressions use eval:

  • setInterval(String, 2)
  • setTimeout(String, 2)
  • new Function(String)

But why should you avoid eval?

It can open up your code for injections attacks (eval of user input - wow, it hurts even to write down, please never do this) and is slow (as it will run the interpreter/compiler) .

Strict mode, please

With this flag you can opt in to use a restricted variant of JavaScript. It eliminates some silent errors and will throw them all the time.

Undeletable properties
'use strict';  
delete Object.prototype; // TypeError  
Object literals must be unique
'use strict';  
var obj = {  
    a: 1, 
    a: 2 
}; 
// syntax error
Prohibits with
var obj = { x: 17 };  
with (obj) // !!! syntax error  
{

}

To get a complete list of these silent errors, visit MDN.

Static code analysis

Use either JSLint, JSHint or ESLint. Static code analysis can catch a lot of potential problems with your code early on.

Testing

I hope it goes without saying: testing, testing and a little bit more testing.

unit testing

Sure, it's not just unit tests - you should shoot for the test pyramid.

Say no to sudo node app.js

I see this a lot: people are running their Node app with superuser rights. Why? Because they want the application to listen on port 80 or 443.

This is just wrong. In case of an error/bug your process can bring down the entire system, as it will have credentials to do anything.

Instead of this, what you can do is to set up an HTTP server/proxy to forward the requests. This can be nginx, Apache, you name it.

Avoid command injection

What is the problem with the following snippet?

child_process.exec('ls', function (err, data) {  
    console.log(data);
});

Under the hood child_process.exec makes a call to execute /bin/sh, so it is a bash interpreter and not a program launcher.

This is problematic when user input is passed to this method - can be either a backtick or $(), a new command can be injected by the attacker.

To overcome this issue simply use child_process.execFile.

For the original blogpost dealing with command injection, please visit LiftSecurity.

Temp files

Pay extra attention when creating files, like handling uploaded files. These files can easily eat up all your disk space.

To deal with this, you should use Streams.

Securing your web application

This part is not just about Node - but about how you should secure your web applications in general.

Reflected Cross Site Scripting

This occurs when an attacker injects executable code to an HTTP response. When an application is vulnerable to this type of attack it will send back unvalidated input to the client (mostly written in JavaScript). It enables the attacker to steal cookies, perform clipboard theft and modify the page itself.

Example

http://example.com/index.php?user=<script>alert(123)</script>

If the user query string is sent back to the client without validation, and is inserted into the DOM, it will be executed.

How to prevent it?
  • never insert untrusted data into the DOM
  • HTML escape before inserting

More info on Reflected Cross Site Scripting and how to avoid it.

Stopping Cookie Theft

By default, cookies can be read by JavaScript on the same domain. This can be dangerous in case of a Cross Site Scripting attack. But not just that: any third-party JavaScript library can read them.

Example

var cookies = document.cookie.split('; ');

How to prevent it?

To prevent this you can set the HttpOnly flag on cookies, which will make your cookies unreachable for Javascript.

Content Security Policy

Content Security Policy (CSP) is an added layer of security that helps to detect and mitigate certain types of attacks, including Cross Site Scripting (XSS) and data injection attacks.

CSP can be enabled by the Content-Security-Policy HTTP header.

Example

Content-Security-Policy: default-src 'self' *.mydomain.com

This will allow content from a trusted domain and its subdomains.

More info and examples on CSP.

Cross-Site Request Forgery

CSRF is an attack which forces an end user to execute unwanted actions on a web application in which he/she is currently authenticated.

It can happen because cookies are sent with every request to a website - even when those requests come from a different site.

Example
<body onload="document.forms[0].submit()">  
  <form method="POST" action="http://yoursite.com/user/delete">
    <input type="hidden" name="id" value="123555.">
  </form>
</body>  

The result of the above snippet can easily result in deleting your user profile.

How to prevent it?

To prevent CSRF, you should implement the synchronizer token pattern - luckily the Node community has already done it for you. In short, this is how it works:

  1. When a GET request is being served check for the CSRF token - if it does not exists, create one
  2. When a user input is showed, make sure to add a hidden input with the CSRF token's value
  3. When the form is sent, make sure that the value coming from the form and from the session are a match.
In practice

To see all this in action you should do the Security Adventure workshopper which will guide you through a real life example on how to secure an Express-based application.

Secure your Express application: Helmet for the rescue

Helmet is a series of middlewares that help secure your Express/Connect apps. Helmet helps with the following middlewares:

  • csp
  • crossdomain
  • xframe
  • xssfilter
  • and much more

For more info and on how to use, check out its repository: https://github.com/evilpacket/helmet.

Tools to use

  • npm shrinkwrap: Locks down dependency versions recursively and creates a npm-shrinkwrap.json file out of it. This can be extremely helpful when creating releases. For more info, pay NPM a visit.

  • retire.js: The goal of Retire.js is to help you detect the use of module versions with known vulnerabilities. Simply install with npm install -g retire. After that, running it with the retire command will look for vulnerabilities in your node_modules directory. (Also note, that retire.js works not only with node modules, but with front end libraries as well.)

Stay updated

If you want to stay updated on potential security vulnerabilities (I hope you do!) then follow the Node Security project. Their goal is to audit every single module in NPM, and if they find issues, fix them.

Need help in developing your application?

RisingStack provides JavaScript development and consulting services - ping us if you need a helping hand!