Application Security

Hacking apps using NoSQL Injection

Application Security
Oct 13, 2022
4 mins
Saumya Kasthuri

This article takes you through an example of an application vulnerable to injection supported by a NoSQL database (MongoDB). In common parlance, a weakness where user input can cause an injection in a NoSQL query to a backend document database, is called NoSQL Injection. We will see what the structure of a NoSQL query looks like and see how we can attack and exfiltrate data.

INTRODUCTION

NoSQL based DBs are everywhere, and a lot of developers are using this over standard RDBMS. Flexibility, Scalability, Ease of use when dealing with semi-structured and unstructured data increases the popularity of NoSQL DB.

The fact that the NoSQL database managers don’t use SQL doesn’t mean that they are free from injection risk. NoSQL injection is like SQL injection as it arises when user input is mixed with query statements on the server, except it targets different technologies such as MongoDB, Redis, Memcached, CouchDB and many more.

How is NoSQL Injection different than standard SQL injection?

The primary difference between SQL and NoSQL injection is the grammar and syntax of thequery. The underlying language used to query RDMS systems and NoSQL databases is very different. Hence, completely altering how the injection is interpreted and what user provided characters will break the query.

 NoSQL database calls are written in the applications programming language, a custom API call,or formatted according to a common convention (such as XML, JSON, LINQ, etc).Malicious input targeting those specifications may not trigger the primarily application sanitization checks. For example, filtering out common HTML special characters such as < > & ; will not prevent attacks against a JSONAPI, where special characters include / { } :

 NoSQL injection attacks may execute in different areas of an application than traditional SQL injection. Where SQL injection would execute within the database engine, NoSQL variants may execute within the application layer or the database layer,depending on the NoSQL API used and data model. Typically, NoSQL injection attacks will execute where the attack string is parsed, evaluated, or concatenated into a NoSQL API call.

 MongoDB,currently one of the most popular NoSQL database products, stores data as documents using a syntax similar to JSON (JavaScript Object Notation). Among other benefits, this allows developers to build full-stack applications using only JavaScript into a web application backed by a NoSQL database.

 Let’s see an example that show show a SQL and a NoSQL query are different for the same functionality.

Typical SQL query for login

  • SELECT * FROM users WHERE user = '$username' AND pass = '$password'

Equivalent command in MongoDB

  • db.users.find({user: username, pass: password});

As you can see from the code above, we are querying the “users” collection and returning the row which contains the specified username and password.

 Injection into MongoDB backed apps

 Now that we have seen a simple query for selecting a user using the username and password, wecan look at the other operators in MongoDB that could allow us to manipulate the query.

Presence of NoSQL injection can be find everywhere irrespective of URL parameters, POST parameters, HTTP headers etc.

Whenever the application accepts the user input that mixes with the code that creates thequery in the app the possibility of injection attacks arises.

Some common signs to look out for during testing

 Examples for popular use cases
 Login bypass

 We are trying to login to the application with the user admin and the password ‘pass

 Typical SQL query for login

  •  SELECT * FROM users WHERE user = ‘$username’ AND pass = ‘$password’

 Equivalent command in MongoDB.

  • db.collection(‘users’).find({‘username’:admin, ‘password’: pass});

 OR

  • app.post('/user', function (req, res) {
          var query = {        
                 username:req.body.username,        
                 password:req.body.password
          }
          db.collection('users').findOne(query, function (err, user) {
                 console.log(user);
          });
    }); 

Username and password would be placed into thequery. Here if the attacker inserts password to {$ne: ‘’}. Since it is matching on a password not equal to a blank string it will result in true.

 As $ne is the not equal operator, this request would return the first user (possibly an admin) without knowing its name or password.

If it is not a JSON request, then the following syntax can be used 

  • POST /login HTTP/1.1
    Host: example.org
    Content-Type: application/x-www-form-urlencoded.
    Content-Length: 29       

    user=admin&password[%24ne]=x

The solution in this case is to sanitize the input before using them. A good option is mongo-sanitize - It will strip out any keys that start with '$' in the input, so you can pass it to MongoDB without worrying about malicious users overwriting.

  • var sanitize = require('mongo-sanitize');
    app.post('/user', function (req, res) {
       var query = {
           username:sanitize(req.body.username),
           password:sanitize(req.body.password)
       }
      db.collection('users').findOne(query, function (err, user) {
          console.log(user);
       });
    });

 Incase of RDBMS an ORM maps between an Object Model and a Relational Database. Similarly, in NoSQL an ODM maps between an Object Model and a Document Database.

 Mongoose is an Object Data Modelling (ODM) library for MongoDB and Nodejs.

 The above login query can be re-written with mongoose as:

  • const UserSchema = new mongoose.Schema({ userName: String, password:String });
                 router.post('/login', (request, response) => {
          const userName = request.body.userName;
           const password = request.body.password;
         User.findOne({ userName: userName }, function (error, user) {
           //... check password, other logic
       });
    });

 If you are using Mongoose, you don't need to sanitize the inputs. In this case, you just need to set the properties to be typed as string. If someone passes an object like { $ne: null },Mongoose will convert it to a string and no harm will be done.

Cross user data retrieval

 Imagine a functionality that allows for content searching, and you want to search the profile of “Alice”.

 For this, you would need to execute a query in the database to find all information about Alice. The query should be:

  • db.customer.find( {name: ‘Alice’})

With the following result:

  • {
    "_id": ObjectId("711d1917170058fe821b34da"),
    "name": "Alice",
    "gender": "female",
    "age": 47,
    "city": "New York"
    }

You design a form in the web application with an input value: the name of the customer (customername).

 If you process the request with the previous query and concatenate the input data, then the resulting query will be like this:

  • db.customer.find( {name:req.body.costomername } )

 This is ok if you input “Alice” but what happens if you input {“$ne”: “nobody”}.

  • db.customer.find( {name: {“$ne”:“nobody”} } )

As we know, the “$ne”operator means “not equal” in MongoDB. So, it will retrieve all customer records whose names are different from “nobody”. All that information can be sent to the frontend if the application does not have any control, which means an attacker might be able to access unauthorized data.

 The $where operator attack

The $where operator has a very dangerous feature: it allows you to pass a string that will be evaluated on the server.

 To reproduce the problem, suppose that you have an online store and want to find out which users have more than X cancelled orders. You could query as the following:

  •  varquery = {
       $where: "this.canceledOrders >" + req.body.canceledOrders
    }
    db.collection('users').find(query).each(function(err,doc) {
       console.log(doc);
    })

 In this case, mongo-sanitize will not help you if the input string is '0; return true'. Your where clause will be evaluated as this.canceledOrders> 0; return true and all users would be returned.

 Or you could receive '0;while(true){}' as input, resulting in a DoS attack.

 It also works for string inputs, like:

  •  varquery = {
       $where: "this.name === '" +req.body.name + "'"
    }

 The attack could be the string '\'; return \'\' == \'' and the where clause would be evaluated to this.name=== ''; return '' == '', that results in returning all users instead of only those who matches the clause.

 The solution here is to avoid the usage of the $where operator and use operators like $eq or $gt instead. 

Possible Mitigations

1. Like most input validation related security issues, to avoid NoSQL injections, you must always treat user input as untrusted. Here is what you can do to validate user input:

                  * Use a sanitization library. For example, mongo-sanitize or mongoose.

                  * If you can’t find a library for your environment, cast user input to the expected type. For example, cast usernames and passwords         to strings.

                  * In the case of MongoDB, never use$where, mapReduce, or group operators with user input because these operators allow the attacker to inject JavaScript and are therefore much more dangerous than others.

                  * For extra safety, set javascriptEnabled to false in mongod.conf, if possible.

2. Always apply the least privilege principle on database users. Run your application with the lowest privileges possible so that even if it gets exploited, the attacker cannot access other resources.

3. npm package “mongo-sanitize” can be used as given as per their documentation as below:

  • var sanitize = require(“mongo-sanitize”);

 The sanitize function will strip out any keys that start with “$” in the input, so you can pass it to MongoDB without worrying about overwriting query selectors.

  •  var clean =sanitize(req.params.username);
    Users.findOne({name: clean},function(err, doc) { });

         If sanitize() is passed an object, it will mutate the original object.

4. Input should be JavaScript escaped or validated before being used to query the database.

5. Apply whitelist validation on all user input, including GET & POST parameters,cookies and other HTTP headers.

6. Use a database ORM instead of raw queries.

7. User explicit comparison operators such as $eq in query expressions rather than allowing implicit equality matching.

CONCLUSION

NoSQL databases have several benefits over standard RDBMS instances over performance, scalability etc. But like all other application, server and database combinations must be subjected to the same security practices to ensure data integrity and access control is maintained.Novel injection attacks could bypass standard security measures put in place for NoSQL databases, if care is not taken to ensure defences are in place for JavaScript and NoSQL based code and configuration. Defence in depth measures coupled with proper input validation and sanitization can go a long way in preventing data breaches for applications of this setup.

HAZE WEBFLOW TEMPLATE

Build a website that actually performs better.

1
Lorem ipsum dolor sit amet consectutar
2
Lorem ipsum dolor sit amet consectutar
3
Lorem ipsum dolor sit amet consectutar