Securing MongoDB from External Injection Attacks

Onyancha Brian Henry

MongoDB security is not fully-guaranteed by simply configuring authentication certificates or encrypting the data. Some attackers will “go the extra mile” by playing with the received parameters in HTTP requests which are used as part of the database’s query process. 

SQL databases are the most vulnerable to this type of attack, but external injection is also possible in NoSQL DBMs such as MongoDB. In most cases, external injections happen as a result of an unsafe concatenation of strings when creating queries.

What is an External Injection Attack?

Code injection is basically integrating unvalidated data (unmitigated vector) into a vulnerable program which when executed, leads to disastrous access to your database; threatening its safety. 

When unsanitized variables are passed into a MongoDB query, they break the document query orientation structure and are sometimes executed as the javascript code itself. This is often the case when passing props directly from the body-parser module for the Nodejs server. Therefore, an attacker can easily insert a Js object where you’d expect a string or number, thereby getting unwanted results or by manipulating your data. 

Consider the data below in a student's collection.

{username:'John Doc', email:'[email protected]', age:20},

{username:'Rafael Silver', email:'[email protected]', age:30},

{username:'Kevin Smith', email:'[email protected]', age:22},

{username:'Pauline Wagu', email:'[email protected]', age:23}

Let’s say your program has to fetch all students whose age is equal to 20,  you would write a code like this...

app.get(‘/:age’, function(req, res){

  db.collections(“students”).find({age: req.params.age});

})

You will have submitted a JSON object in your http request as 

{age: 20}

This will return all students whose age is equal to 20 as the expected result and in this case only {username:'John Doc', email:'[email protected]', age:20}

Now let’s say an attacker submits an object instead of a number i.e {‘$gt:0’};

The resulting query will be:

db.collections(“students”).find({age: {‘$gt:0’}); which is a valid query that upon execution  will return all students in that collection. The attacker has a chance to act on your data according to their malicious intentions. In most cases, an attacker injects a custom object that contains MongoDB commands that enable them to access your documents without the proper procedure.

Some MongoDB commands execute Javascript code within the database engine, a potential risk for your data. Some of these commands are ‘$where’, ‘$group’ and ‘mapReduce’. For versions before MongoDB 2.4, Js code has access to the db object from within the query.

MongoDB Naitive Protections

MongoDB utilizes the BSON data (Binary JSON) for both its queries and documents, but in some instances it can accept unserialized JSON and Js expressions (such as the ones mentioned above). Most of the data passed to the server is in the format of a string and can be fed directly into a MongoDB query. MongoDB does not parse its data, therefore avoiding potential risks that may result from direct parameters being integrated. 

If an API involves encoding data in a formatted text and that text needs to be parsed, it has the potential of creating disagreement between the server’s caller and the database’s callee on how that string is going to be parsed. If the data is accidentally misinterpreted as metadata the scenario can potentially pose security threats to your data.

Examples of MongoDB External Injections and How to Handle Them

 Let’s consider the data below in a students collection.

{username:'John Doc', password: ‘16djfhg’, email:'[email protected]', age:20},

{username:'Rafael Silver',password: ‘djh’, email:'[email protected]', age:30},

{username:'Kevin Smith', password: ‘16dj’, email:'[email protected]', age:22},

{username:'Pauline Wagu', password: ‘g6yj’, email:'[email protected]', age:23}

Injection Using the $ne (not equal) Operator

If I want to return the document with username and password supplied from a request the code will be:

app.post('/students, function (req, res) {

    var query = {

        username: req.body.username,

        password: req.body.password

    }

    db.collection(students).findOne(query, function (err, student) {

        res(student);

    });

});

If we receive the request below

POST https://localhost/students HTTP/1.1

Content-Type: application/json

{

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

    "password": {"$ne": null}

}

The query will definitely return the first student in this case since his username and password are not valued to be null. This is not according to the expected results.

To solve this, you can use:

mongo-sanitize module which stops any key that starts with‘$’ from being passed into MongoDB query engine.

Install the module first  

​npm install mongo-sanitize

var sanitize = require(‘mongo-sanitize’);

var query = {

username: req.body.username,

password: req.body.password

}

Using mongoose to validate your schema fields such that if it expects a string and receives an object, the query will throw an error. In our case above the null value will be converted into a string “” which literally has no impact.

Injection Using the $where Operator

This is one of the most dangerous operators. It will allow a string to be evaluated inside the server itself. For example, to fetch students whose age is above a value Y, the query will be 

var query = { 

   $where: “this.age > ”+req.body.age

}

 db.collection(students).findOne(query, function (err, student) {

        res(student);

    });

Using the sanitize module won’t help in this case if we have a ‘0; return true’ because the result will return all the students rather than those whose age is greater than some given value. Other possible strings you can receive are ‘\’; return \ ‘\’ == \’’ or  this.email === ‘’;return ‘’ == ‘’. This query will return all students rather than only those that match the clause.

The $where clause should be greatly avoided. Besides the outlined setback it also reduces performance because it is not optimized to use indexes.

There is also a great possibility of passing a function in the $where clause and the variable will not be accessible in the MongoDB scope hence may result in your application crashing. I.e

var query = {

   $where: function() {

       return this.age > setValue //setValue is not defined

   }

}

You can also use the $eq, $lt, $lte, $gt, $gte operators instead.

Protecting Yourself from MongoDB External Injection

Here are three things you can do to keep yourself protected...

  1. Validate user data.  Looking back at how the $where expression can be used to access your data, it is advisable to always validate what users send to your server.
  2. Use the JSON validator concept to validate your schema together with the mongoose module.
  3. Design your queries such that Js code does not have full access to your database code.

Conclusion

External injection are also possible with MongoDB. It is often associated with unvalidated user data getting into MongoDB queries. It is always important to detect and prevent NoSQL injection by testing any data that may be received by your server. If neglected, this can threaten the safety of user data. The most important procedure is to validate your data at all involved layers.

ClusterControl
The only management system you’ll ever need to take control of your open source database infrastructure.