DEV Community

Cover image for Thoughts on How to Prevent NoSQL Injection for Node.js Express Server
Yang Li
Yang Li

Posted on

Thoughts on How to Prevent NoSQL Injection for Node.js Express Server

NoSQL injection is a type of vulnerability where an attacker is able to inject arbitrary text into NoSQL queries.

NoSQL injections are very similar to the traditional SQL injection attack, except that the attack is against a NoSQL database. NoSQL is a general term for any database that does not use SQL, a common database management system (DBMS) that utilizes NoSQL is MongoDB.

NoSQL injection diagram

In this post, Iā€™d like to describe the followings to show how to prevent NoSQL injection:

  1. Build a simple NodeJS app
  2. Understand what a NoSQL Injection attack is
  3. Use a npm package to prevent attacks

For prerequisites, let us assume that we have a ready-to-use MongoDB cluster or a local MongoDB installation and have the connection URI, e.g. http://localhost:27017.

Project Setup

Let us initialize our node.js app and install a couple of packages. Run the following commands in an empty folder.



$ npm init -y
$ npm i express mongoose 
$ npm i nodemon dotenv -D


Enter fullscreen mode Exit fullscreen mode

Or, use Yarn instead.



$ npm init -y
$ yarn add express mongoose 
$ yarn add nodemon dotenv ā€“-dev


Enter fullscreen mode Exit fullscreen mode

Create app.js and models/user.model.js.

app.js



require('dotenv').config();

const express = require('express');
const mongoose = require('mongoose');

const routes = require('./routes/index');

const app = express();

app.use(express.json());

app.use(routes);

mongoose
  .connect(process.env.MONGODB_URI)
  .then(() => {
    console.log('Mongoose connected šŸƒ');
    app.listen(3000, () => {
      console.log('Server is up and running šŸš€');
    });
  })
  .catch((error) => {
    console.log(error);
  });


Enter fullscreen mode Exit fullscreen mode

models/user.model.js



const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
  username: {
    type: String,
    unique: true
  },
  password: {
    type: String,
    unique: true
  }
});

const User = mongoose.model('User', userSchema);

module.exports = User;


Enter fullscreen mode Exit fullscreen mode

With the setup out of the way, let us start by looking at a simple NoSQL attack.
For demonstration purposes, I'll create a simple routing file.

routes/index.js



const express = require('express');

const User = require('../models/user.model');

const router = express.Router();

router.get('/users', async (req, res, next) => {
  return res.json({
    users: await User.find({}).exec()
  });
});

router.post('/login', async (req, res, next) => {
  const { username, password } = req.body;

  const user = await User.findOne({ username, password }).exec();

  res.json({
    message: `Logged in as ${user.username}`
  });
});

module.exports = router;


Enter fullscreen mode Exit fullscreen mode

It's time to make a GET request to /users to see what we have. As mentioned previously, we will be using Postman.



// GET localhost:3000/users

{
    "users": [
        {
            "_id": "6125401c491c0b0bfd123165",
            "username": "test1",
            "password": "123456",
            "__v": 0
        },
        {
            "_id": "6125401c491c0b0bfd123166",
            "username": "test2",
            "password": "abcdef",
            "__v": 0
        },
        {
            "_id": "6125401c491c0b0bfd123167",
            "username": "test3",
            "password": "sdjndsn",
            "__v": 0
        }
    ]
}


Enter fullscreen mode Exit fullscreen mode

This is the response that the server returns. As seen from the code block above, we have three users (Passwords are shown for demonstration purposes) for example.

Next, try to log in using one of the credentials. Make a POST request to /login with the request body below.



{
  "username": "test1",
  "password": "123456"
}


Enter fullscreen mode Exit fullscreen mode

We would get the following response. Everything seems pretty normal up to this point, so let us dive deeper!



// POST localhost:3000/login

{
    "message": "Logged in as test1"
}


Enter fullscreen mode Exit fullscreen mode

Perform a demo NoSQL injection!!!

NoSQL Injection

To perform a NoSQL injection attack, simply change the password value in the body from "123456" to { "$ne": "null" }. So, the new request body looks like the following:



{
  "username": "test1",
  "password": { "$ne": null }
}


Enter fullscreen mode Exit fullscreen mode

Hold on, please!
What does the $ne operator do? Let me explain.

$ne operator tells mongo to check for not equal to. That is, instead of checking the password for the correct value, MongoDB will simply check whether the password field is null or not. Since the password field cannot be null in any case, we can trick MongoDB into giving us the user information without knowing the password.

After making a POST request to /login with the new modified request body, we would get the following response from the server.



// POST localhost:3000/login

{
    "message": "Logged in as test1"
}


Enter fullscreen mode Exit fullscreen mode

Oh my god!šŸ˜±

There is more than one way to do the same thing. For instance, we could have used { "$gt": "" } instead of the $ne operator.

Fortunately, preventing this attack is as easy as performing it. Next, let us take a look at how we can prevent NoSQL injection attacks.

Introduce NPM module

We would install the express-mongo-sanitize package from NPM. Run the following command:



$ npm i express-mongo-sanitize


Enter fullscreen mode Exit fullscreen mode

Or,



$ yarn add express-mongo-sanitize


Enter fullscreen mode Exit fullscreen mode

We can learn the details about this module here:
https://www.npmjs.com/package/express-mongo-sanitize

Requiring the package returns a middleware function, which can then be used as in the example below.



// Prevent NoSQL injection
const mongoSanitize = require('express-mongo-sanitize');

...

// Prevent NoSQL injection
app.use(
  mongoSanitize({
    onSanitize: ({ req, key }) => {
      // Throw an error
      // Catch with express error handler
    }
  })
);


Enter fullscreen mode Exit fullscreen mode

I hope this post would be a bit helpful to prevent NoSQL injection attacks when you implement your node.js express server.

For a full source code, please refer to this GitHub repo:
https://github.com/liyang51827/express-nosql-injection-demo

Top comments (0)