Backend Security Essentials : NodeJS Edition

Node.js, like any other framework or programming language, is prone to all kinds of web application vulnerabilities. The core of Node.js is secure, but third-party packages may require additional security measures to protect your web applications. According to the research, 14% of the Node Package Manager (NPM) ecosystem is affected. The indirectly affected packages are estimated to be about 54% of the ecosystem.

Open source applications inherit any security and licensing issues from their open source components. The problem is that security testing tools like dynamic and static code analysis are ineffective at detecting open source vulnerabilities.

Following are some of the attacks that can be done on NodeJS server and how we can avoide those attacks with help of npm packages(dependencies)..

Preventing DOS Attacks

1.Limiting inflow data to our server - First thing to consider when dealing with DOS attacks prevention is to limit the actual payload that user can submit to your app / api / service. You can limit the body payload using body-parser. If you are using ExpressJS as your backend framework, then you are golden. ExpressJS comes with built-in body-parser that you can use.

const express = require('express');
const app = express();
app.use(express.json({ limit: '10kb' })); // Body limit is 10
  1. Specifying calls a user can make in specified time duration - Another useful express feature is express-rate-limit dependency. This dependency lets you set rate limit for users. So basically, you can set maximum amount of requests for each user, after user uses all of his requests, you can lock him out for certain amount of time.

    Here is how you can configure express-rate-limit:
    npm install express-rate-limit - Installing it using NPM

    const limit = rateLimit({
        max: 100,// limit each IP to 100 max requests per windowsMS
        windowMs: 60 * 60 * 1000, // 1 Hour
        message: 'Too many requests' // message to send
    });
    
    app.use('/routeName', limit); // Setting limiter on specific route
    
    app.use(limiter); // apply to all requests
    
    app.use("/api/", apiLimiter); // only apply to requests that begin with /api/
    

Preventing XSS Attacks

  1. Data sanitization against XSS - First of all, you can sanitize user data, on input. This is very easy to implement, and we can use another useful dependency called xss-clean.
    This dependency will prevent users from inserting HTML & Scripts on input.

    Here is how you can configure xss-clean:
    npm install xss-clean - Installing it using NPM

    const express = require('express');
    const app = express();
    
    /* make sure this comes before any routes */
    app.use(xss())
    
  2. Setting special HTTP headers for project - Give your project special HTTP headers using helmet dependency. Helmet is a collection of middleware functions. By default, not all of the middleware functions are included, but you can enable rest of them manually. You can check this link to see other middleware functions.

    npm install helmet
    
    const express = require("express");
    const helmet = require("helmet");
    
    const app = express();
    
    app.use(helmet());
    
    
  3. If you are using JSON Web Tokens (JWT) instead of express-session for example, you should consider storing JWT’s into the cookies. As well, make sure these cookies for JWT storing are HTTP Only!

Preventing Brute Force Attacks

  1. One of the most efficient way to help you to deal with brute force attacks, is to set limit to login attempts, or anything related to authentication that requires users to insert their passwords, special codes or PINs.

  2. Next up, and again, if you are using ExpressJS, you could implement express-rate-limit dependency. Which we did indeed implemented in DOS attacks prevention. But this dependency works both for Brute Force attacks and DOS attacks.

  3. To slower down and make life a little bit harder for attackers when guessing sensitive data (passwords, PINs), you could implement bcrypt dependency. Bcrypt will encrypt sensitive data such as passwords and it will make them harder to guess.

  4. Another thing that will make brute force attacks less likely is implementing 2-Step verification process, or two-factor authentication. It does take couple of lines of codes to implement this.

Preventing SQL/NoSQL Injection Attacks

  1. Sanitizing data - Either if you are working with SQL or NoSQL database, you should sanitize your data.
    If you are working with NoSQL database (MongoDB) and ExpressJS, consider using express-mongo-sanitize dependency.

    npm install express-mongo-sanitize
    
    const express = require('express');
    const bodyParser = require('body-parser');
    const mongoSanitize = require('express-mongo-sanitize');
    
    const app = express();
    
    app.use(bodyParser.urlencoded({extended: true}));
    app.use(bodyParser.json());
    
    // To remove data, use:
    app.use(mongoSanitize());
    
    // Or, to replace prohibited characters with _, use:
    app.use(mongoSanitize({
      replaceWith: '_'
    }))
    
  2. If working with MongoDB, use Object Data Modeling tool (ODM) Mongoose. Mongoose lets you define schemas and schema types for each one of your documents, making it secure from the beginning

Securing REST API

For securing rest api we should use tokens that are generated and signed with some data. Mostly we use JWT tokens in node js which help us to secure our api from unauthenticated users. (Token based access)

Link for reference:
https://www.freecodecamp.org/news/securing-node-js-restful-apis-with-json-web-tokens-9f811a92bb52/

We can restrict api based on role as follows: (Role based access)

//Here only admin and super admin role users can create 
app.post(‘/model/create’,restrictAccess([‘SUPER_ADMIN’,’ADMIN’]), Model.create)

const restrictAccess = (userRoles, allowedRoles) =>{
    if(!(_.intersection(allowedRoles, userRoles).length)){
        throw createError(‘PERMISSION_NOT_EXISTS’,403)
    }
}

We can restrict access in within code also for role based as follows:

app.post(‘/model/create’,restrictAccess([‘CHAIR’,’MEMBER’,’SUPER_ADMIN’]), Model.create)

//Here member doesn’t have access to some specific type create else he can create data
if(body.type === ‘SOMETYPE’ && (_.includes(user.roles,’MEMBER’))){
    throw createError(‘UNAUTHORISED’,401)
}

Other reference links are as follows:
https://medium.com/@nodepractices/were-under-attack-23-node-js-security-best-practices-e33c146cb87d
https://dzone.com/articles/10-nodejs-security-practices
https://medium.com/@nodepractices/were-under-attack-23-node-js-security-best-practices-e33c146cb87d