Blog/

How to Secure your NodeJs Express Javascript Application - part 2

Express is an easy-to-use web framework for NodeJs. In this article, we will review some of the common vulnerabilities in NodeJs Express projects and explain mitigations against them.

This is the second part of the NodeJs Express Security series. If you haven’t read the previous part you can read it on the below URL.

How to Secure your NodeJs Express Javascript Application - part 1

Let’s see how we can make our Express software more secure.

Limit input

Allowing clients to send huge request body can be abused for a denial of service (DoS) attack. To prevent DoS attacks always set a maximum request size for the input. Use the below code to limit input size.

var contentType = require('content-type')
var express = require('express')
var getRawBody = require('raw-body')

var app = express()

app.use(function (req, res, next) {
  getRawBody(req, {
    length: req.headers['content-length'],
    limit: '1mb',
    encoding: contentType.parse(req).parameters.charset
  }, function (err, string) {
    if (err) return next(err)
    req.text = string
    next()
  })
})

You can also set request size for different content types:

app.use(express.urlencoded({ extended: true, limit: "1kb" }));
app.use(express.json({ limit: "1kb" }));

Other than input size you should limit the Transaction per Second/Minute (TPS/TPM) rate. If you don’t set a rate limitation for your sensitive APIs like login, you expose yourself to a Brute Force attack. Packages like express-brute, express-bouncer and express-limiter are some sample packages you can use for limiting input rate in Express. Below code is a sample code for protecting against brute force attacks.

const express = require('express')
const ExpressBrute = require('express-brute');
const login = require('./routes/login.js')

const app = express()
 
// using memory store, an in-memory db is recommended for production
let store = new ExpressBrute.MemoryStore();
let bruteforce = new ExpressBrute(store);
 
app.post('/auth', bruteforce.prevent, login);

Note: If you use an API gateway like Kong you can easily configure input limiting policies for your routes.

Note: CAPTCHA and account lockout are also other common solutions for mitigating brute-forcing attacks.

Bonus: You can also benefit from packages like toobusy-js for monitoring the load of your server to detect whether you’re under a DoS attack.

Remove old, forgotten, debug, and unused routes

It’s common (but not recommended) to add debug routes during development for testing purposes. These routes usually don’t have authentication and they’re less restricted. The path to these routes is usually easy to guess; something like /test. This allows attackers to find them easily and abuse the functionality.

Review all the routes in your Express project and remove any unnecessary routes and use proper testing and versioning scheme to prevent unwanted routes in production.

Bonus: Another example of unwanted routes are default routes like those available in Sails and Feathers. Fortunately Express only has a default error and 404 handlers which we will cover in the following section.

Catch 404 and 500 errors

You should always expect exceptions to happen, so get prepared. Unhandled exceptions throw a ton of information to users. Default NodeJs exceptions disclose information like Operation System and used libraries.

Express JavaScript Detailed Error

Instead of displaying a detailed error message, you can add an error-handling middleware to catch all errors like below.

app.use((err, req, res, next) => {
 console.error(err.stack)
 res.status(500).send('Something broke!')
})

Note: You should add the above middleware after any other app.use() and routes. No need to mention that you can capture any exception in NodeJs by subscribing to uncaughtException event on the process object.

Write clean code

Code quality is directly correlated to software security. Less code hides fewer vulnerabilities, right? So does the clean code. Keeping your Express project as clean as possible helps to increase the security of your JavaScript application.

One example of bad code that is common in JavaScript projects is the Callback Hell. Callback Hell, also known as Pyramid of Doom, is an anti-pattern seen in code of asynchronous programming. It happens when callbacks are nested inside each other. It’s easy to get lost in a callback hell because it’s hard to follow the execution flow of the program. Inside those deep nested callbacks security bugs, unhandled exceptions, and information leakage are waiting for the right moment to strike.

JavaScript Promise Hell Refactored

It’s important to have a proper Secure SDLC process in place to prevent bad code. Things like enabling security linters, code reviews, and automatic code scan like Github code scan (see this repo) are good controls for having more secure code.

Only return what is necessary

When passing information to the clients always return the minimum possible fields. Consider a use-case where you want to show a list of available users on your website. You can query the database for all users and return a list of users with all their attributes and only display the username on the front end. Properties like username, password, address, social security numbers, and many others are usually associated with users. You might not display all of them on the page but you are exposing them in your API which makes them available for hackers.

Do not use eval

There are some functions you must avoid calling unless you absolutely have to use them. eval() is one of them. The eval() function evaluates JavaScript code represented as a string. It is far too easy for a bad actor to run arbitrary code when you use eval() with user-supplied input. The NodeJs child_process.exec is similar to eval() and should be avoided.

The fs and vm modules as well as the RegExp class should be used carefully if you want to pass user input to them. Please read the input validation on part 1 of this series for more.

Use strict mode

JavaScript’s strict mode, introduced in ECMAScript 5, is a way to opt-in to a restricted variant of JavaScript. Strict mode makes several changes to normal JavaScript semantics. It eliminates some JavaScript silent errors by changing them to throw errors. It also helps JavaScript engines perform optimizations and prohibits some syntax likely to be defined in future versions of ECMAScript. For all these improvements it’s encouraged to always use strict mode. For activating strict mode add 'strict mode'; at top of your code.

Log and monitor events

Record any events on your application and review them. Monitoring logs regularly could help identify malicious activities on your system. When something bad happens, logs are your only way to find out what happened and what is the root cause of it. In Express you can use express-winston for logging.

Scan security of your website with SmartScanner for free

Download