This is a republished blog post by Gergely Nemeth from RisingStack. They do Full Stack Javascript Development and Consulting. Gergely loves contributing to open-source projects like node-restify, organizing conferences, DevOps, Microservices and cycling. You can find his original article here.
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.
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.
hbspt.cta.load(1169977, '11903a5d-dfb4-42f2-9dea-9a60171225ca', {});
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:
When a
GET
request is being served check for the CSRF token - if it does not exists, create oneWhen a user input is showed, make sure to add a hidden input with the CSRF token's value
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.
We want to thank Gergely for making his original article available for our readers.