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.
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).
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.
'use strict'; delete Object.prototype; // TypeError
'use strict'; var obj = { a: 1, a: 2 }; // syntax error
var obj = { x: 17 }; with (obj) // !!! syntax error { }
To get a complete list of these silent errors, visit MDN.
Use either JSLint, JSHint or ESLint. Static code analysis can catch a lot of potential problems with your code early on.
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.
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.
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.
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.
This part is not just about Node – but about how you should secure your web applications in general.
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.
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.
More info on Reflected Cross Site Scripting and how to avoid it.
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.
var cookies = document.cookie.split('; ');
To prevent this you can set the HttpOnly
flag on cookies, which will make your cookies unreachable for Javascript.
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.
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.
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.
<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.
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:
GET
request is being served check for the CSRF token – if it does not exists, create oneTo 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.
Helmet is a series of middlewares that help secure your Express/Connect apps. Helmet helps with the following middlewares:
For more info and on how to use, check out its repository: https://github.com/evilpacket/helmet.
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.)
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.