Hacking NodeJS and MongoDB

What I would like to show you is a simple technique that can be effectively used against modern web applications, such as those written on top of NodeJS and MongoDB. In essence, this technique is very similar to SQL Injection (SQLI) although much simpler because we do not have to complete any weird and complicated strings.

nodejs and mongodb

Before I move on, I would like to state that although I talk about NodeJS, in the examples below I use ExpressJS, which is the most popular web framework for node and a de facto standard in the NodeJS community.

The SQL Injection Primer

The first thing you learn when studying SQL Injection is how to create true statements. Let's consider the following example SQL statement that is used to authenticate the user when the username and the password are submitted to the application:

SELECT * FROM users WHERE username = '$username' AND password = '$password'

If this statement is not prepared or properly handled when constructed, an attacker may be able to place ' or 1=1-- in the username field in order to construct a statement that looks more or less like the one bellow, which is known as the classic login bypass via SQLI:

SELECT * FROM users WHERE username = '' or 1=1--' AND password = ''

Even today, this classic attack and its variations are wildly used to detect the presence of improper handling of SQL statements.

The MongoDB Injection Primer

Now, even though SQL Injection is still a popular attack vector, it is no longer as widespread as it used to be. Many modern web applications opt in to use a much simpler storage mechanism such as the one provided by NoSQL databases like MongoDB. NoSQL databases not only promise simplified development but also improved security by eliminating the SQL language entirely and relaying on much simpler and structured query mechanism that is typically found in the face of JSON and JavaScript.

The SQL statement that we used above to query the user login details will be written like this in MongoDB:

db.users.find({ username: username, password: password })

As you can see we no longer deal with a query language in the form of a string therefore one would think that injection is no longer possible. And of course, as it is always the case with security, they will be wrong because there are many factors at play.

For example, if we assume that the username field, or parameter if you like, is coming from a deserialized JSON object, manipulation of the above query is not only possible but inevitable. Such as, if one supplies a JSON document as the input to the application, an attacker will be able to perform the exact same login bypass that was before possible only with SQL injection:

{
  "username": { "$gt": "" },
  "password": { "$gt": "" }
}

The actual vulnerable handler of the request will look more or less like this:

app.post('/', function (req, res) {
  db.users.find(
    { username: req.body.username, password: req.body.password },
    function (err, users) {
      // TODO: handle the rest
    }
  )
})

In the above ExpressJS handler, the username and password fields are not validated to ensure that they are strings. Therefore, when the JSON document is deserialized, those fields may contain anything but strings that can be used to manipulate the structure of the query. In MongoDB, the field $gt has a special meaning, which is used as the greater than comparator. As such, the username and the password from the database will be compared to the empty string "" and as a result return a positive outcome, i.e. a true statement.

The request to exploit this vulnerability will look more or less like the one bellow. Use this link to open the request in Rest:

POST http://target/ HTTP/1.1
Content-Type: application/json

{
	"username": {"$gt": ""},
	"password": {"$gt": ""}
}

Taking NodeJS and MongoDB Exploitation Further

In the example above I deliberately choose to use JSON as the transport mechanism because it makes this attack easier to explain. While, it is not unusual to see JSON documents as the communication mechanism, they are not as widespread as url-encoded key-value pairs, simply known as urlencoding. One would think that if you just use body and query parameters in urlencoding format than you will be safe.

In ExpressJS we can still achieve the bypass effect without using JSON at all but simple query strings. For example, we can submit a request like the one illustrated bellow. Use this link to open the request in Rest:

POST http://target/ HTTP/1.1
Content-Type: application/x-www-form-urlencoded

username[$gt]=&password[$gt]=

The string username[$gt]= is a special syntax used by the qs module (default in ExpressJS and the body-parser middleware). This syntax is the equivalent of making an JavaScript object/hash with a single parameter called $gt mapped to no value. In essence, the request above will result into a JavaScript object that looks like the one illustrated bellow:

{
  "username": { "$gt": undefined },
  "password": { "$gt": undefined }
}

If you compare this object with the one that we used in the previous example, you can see that logically they are the same.

Once again, we have defeated the authentication mechanism to login as the first user in the database which is probably the administrator. This technique can be extended even further with additional MongoDB operators as per the official documentation here.

Learning By Example

I believe that the best way to learn new things is to experience them first hand. Therefore, I have created two projects that illustrate this vulnerability in practice. The first project uses normal urlencoded form values while the second project uses JSON (AngularJS).

github screenshot of no-login

Both projects can be found on our GitHub portal here and here. You need docker in order to run them. I will save myself the trouble going into details about how docker works. Just read the projects' README file for more information.

Testing For MongoDB Injection

I hope you now have a good idea the kind of attacks we can perform against NodeJS and MongoDB applications. In practice, I have seen a great success using the technique above and its variations when performing manual investigations. However, I am also a big fan of automation so, for the fans of Websecurify Suite, I have personally incorporated these tests in both Formfuzz and Jsonfuzz tools.

To make it simpler, I have implemented a new escapemode command called use_mongodb_payloads. Once you execute the command, the fuzzer will not only use MongoDB specific payloads but also do mutation of the query to achieve maximum depth.

Formfuzz escapemode

Once the escapemode command is executed, the fuzzer will produce requests similar like the one illustrated on the screenshot bellow. As you can clearly see we bypassed the login screen in just a few seconds by using the Formfuzz fuzzer.

Formfuzz MongoDB fuzzing in action

I hope this post helps you get some understanding about the kind of problems you may experience if you are using NodeJS and MongoDB together. In a follow-up post I will go in depth about other security issues effecting both platforms. If you have any specific questions, do not hesitate to get in touch. We are here to help.