SBN

Mitigating NoSQL Injection Attacks: Part 2

This is the second part of a two-part series on NoSQL injections. Last time, we covered the anatomy of a NoSQL injection, as well as how to mitigate it. In this post, we will look at specific injection attack types, namely Server-Side JavaScript and Blind NoSQL injections.

In our last article, we looked at how malicious parties could run queries against a NoSQL database, as well as how to mitigate such risks. In this article, we will take a look at two more specific types of injection attacks against NoSQL databases.

Server-Side JavaScript Injections on NoSQL Databases

Server-side JavaScript (SSJS) Injections occur when a server-side component allows the attacker to execute JavaScript code in the server context. Sometimes, this is allowed to provide developers with extended functionality. However, in the wrong hands, this capability lets malicious parties provide untrusted data that are then interpreted and executed by the server.

Some of the more commonly used functions for server-side JavaScript injection attacks include:

  • eval()
  • setTimeout()
  • setInterval()
  • Function()

All of these functions will parse arguments originating from a user-controlled source of input data.

SSJS: An Example

MongoDB’s Evaluation Query operator $where allows you to match documents when they satisfy a JavaScript expression. For example, there’s:

db.users.find( { $where: function() { return (this.country == ‘VA’); }});

which can be shortened to

db.users.find( { $where: this.country == ‘IL’ }});

Whenever this query executes, MongoDB finds and returns all of the user’s collection documents where their country field equals the string VA.

This is risky, and if you allow users to pass unsanitized parameters to the evaluated JavaScript expression for the $where operator, you’ve introduced a security vulnerability. To see how a malicious attacker could exploit this vulnerability, the following ExpressJS sample app defines an HTTP GET call to the /userCountries API endpoint (though this would also work with POST and PUT calls), which then executes a MongoDB injection with the $where operator:

app.get(‘/userCountries’, function(req, res) {
  var country = req.query.country;
  var searchCriteria = “this.country == “ + “‘“ + country + “‘“;
  users.find({
    $where: searchCriteria
    }).toArray(function(err, response) {
    res.render(‘users’, { users: response });
  });
});

The searchCriteria variable builds the where expression based on user input, so the exact query looks like the following:

$where: “this.country == ‘VA’”

You can see how this query resembles a traditional SQL injection. Because the $where operator evaluates JavaScript, the example above showing the combining of user input with a MongoDB query results in the following problematic scenario:

  1. The attacker sends an HTTP GET request that satisfies the $where operator. The request provides text to the Boolean expression and closes the expression with a single quotation mark.
  2. With the string expression terminated, the attacker can add any valid JavaScript code to the request. With an additional single quote to close the expression, everything gets concatenated:
curl “http://localhost:31337/user?country=VA';while(true)\{\};'”

This results in the following $where operator expression:

$where: “this.country == ‘VA’;while(true){};’’”

This expression triggers a Denial of Service (DoS) attack on the MongoDB service. While the attacker hasn’t chosen to exploit Node.js here (and it can, therefore, continue to serve requests), additional requests to the Node.js server requiring MongoDB fails.

At this point, MongoDB is using up 100% of the CPU’s resources in an infinite loop that ends only the watchdogs and timers kick in.

Mitigating SSJS Injection Attacks

If you’re working with MongoDB, you should probably avoid using the $where operator if at all possible. However, the following are general, best practice guidelines on avoiding and mitigating NoSQL injection attacks:

  1. Sanitize and validate user input: Do not allow input originating from users to be used without sanitization and validation; make sure that what you’re allowing and expecting matches what the user has provided
  2. Use prepared statements: Prepared statements have been used to prevent SQL injection attacks, and similarly, they can be used in NoSQL situations as well. Prepared statements allow you to secure the data
  3. Avoid using JavaScript functions to parse user input: Avoid using eval(), setTimeout(), setInterval(), or Function()to parse user-provided input

Blind NoSQL Injections

The purpose of a blind injection attack is to get as much information from the database as possible; even if a direct injection fails, the attacker uses the information gleaned to refine and create new queries. Over time, the attacker learns more and more about the database’s structure and its contents.

One variant of a blind noSQL injection exists in MongoDB and allows malicious parties to use operators, including $regex, which helps them gather information by varying the regular expressions sent as input to the database.

The Risk from Blind NoSQL Injections

In an example above, we looked at a trivial example where the attacker tested username and password inputs against the database:

User.find({ username: req.body.username, password: req.body.password}, function(err, document) {})

The risks of such actions are clear, so you might assume that the next step in validating such input to be checking the provided password against the one stored in the database.

One approach might be to find the user record and invoke a callback function to see if the two passwords match:

function userAuth(req) {
  Users.findOne({username: req.body.username}, compareUserPass);
  function compareUserPass(err, dbUserRecord) {
    return dbUserRecord.password === req.body.password;
    // Tip: next time compare a provided password to a stored hash
    // in the database, so that you never store plaintext passwords.
  }
}

The proposed solution supposedly enhances security by comparing the plain text password in the database against the plain text input from the user. This should mitigate user input like:

{username: {“$gt”: “”}, password: {“$gt”: “”}}

Even if the attacker finds a user from the database using the $gt operator, the database compares the passwords and expects them to match.

The Attack Scenario and Mitigation

The solution proposed above seems like it should solve problems, but you should be wary (especially when it comes to using the username without sanitization).

Since we compare the passwords to find exact matches, the attack vector would be to brute force try to match a record with a specified password. If the username is unsanitized, we could use other MongoDB operators to find user records that are based on specific patterns by ending the query:

curl -X POST -H “Content-Type: application/json” — data ‘{“username”:{“regex”: demo”}, “password”:”123456"}’ http://localhost:31337/login

If an account exists where the username is demo and the password is 123456, then we get a new session and we’ve successfully exploited a loophole in the database.

In real systems, there are a large number of users, so the attacker would need more complex regex, as well as brute force attempts to find records:

{“username”: {“regex”: “^demo$”}, “password”: “123456”}
{“username”: {“regex”: “^demo1$”}, “password”: “123456”}
{“username”: {“regex”: “^demo2$”}, “password”: “123456”}

As you can see, understanding how and why a blind NoSQL injection attack works are needed when implementing mitigation strategies. At a minimum, all input must be validated and sanitized, as we’ve reviewed previously.


Mitigating NoSQL Injection Attacks: Part 2 was originally published in ShiftLeft Blog on Medium, where people are continuing the conversation by highlighting and responding to this story.


*** This is a Security Bloggers Network syndicated blog from ShiftLeft Blog - Medium authored by Katie Horne. Read the original post at: https://blog.shiftleft.io/mitigating-nosql-injection-attacks-part-2-42a2d8890f70?source=rss----86a4f941c7da---4