Edit: The follow-up on how to prevent NoSQL injections in MongoDB in a Node.js app can be found here.

Tl;dr – Mongo $and Node.js $ne safe

Using a NoSQL database does not mean that injections are not possible. This article shows how a Node.js application based on Express and using MongoDB (with Mongoose ORM) can be vulnerable to NoSQL injections.

Good ol’ SQL injections

SQL injection is a pretty well-known attack. A SQL injection happens when a client sends SQL queries to a server and when those queries are executed.

Usually, a SQL injection is possible because of an unsafe string concatenation when creating a SQL query.

For instance, the following Express route pictures a vulnerable piece of code:

The developer expects the request parameter ‘id’ to be a string representing the id of an item in the database. The HTTP requests are expected to look like: GET /posts/150.

However, it is possible to inject arbitrary SQL code here, for instance, in order to get all items in the database, an attacker could perform the request GET ‘/posts/0 OR TRUE’.

In this situation, the attacker can do pretty much everything he wants with the database. There is a large literature regarding this topic if that’s something you are interested in.

MongoDB (and other NoSQL database) does not use SQL language for queries.

We will focus on MongoDB (since it is one of the most popular database within the Node.js community), however, the concepts described here apply to other NoSQL databases engines.

With Node.js official MongoDB driver, requests are performed using a query API with query objects. For instance, querying all items in a collection whose ‘quantity’ field is greater than or equal to 0 would be done using:

We used the find method on the Items collection. The query object states that we want to filter on the quantity field and we used a MongoDB command $gte which means ‘greater than or equal’ to state our request.

If we wanted only the items whose quantity is 0, we could do:

Other methods that find exist and are referenced in the driver’s API.

NoSQL means Not-injectable, right?

We first need to define what an injection would be here:

A MongoDB injection happens when a client is able to inject MongoDB commands that will be executed by the database engine.

In other words, the attacker will try to inject a custom object with MongoDB commands inside the query object. This would allow him to access more documents than what he should.

In order to have an understandable example, we will use a very simple and naive project. You can find the whole code on Github. This projects has an endpoint to perform smart queries inside the Document collection:

The developer expects the payload of this request to contain a member named ‘title’ or a member named ‘type’. He also does not want the client to be able to query documents which type is ‘secret projects’.

If we perform a few queries on this endpoint, we get the expected result:

Payload Result {“type”:”secret projects”} Empty array { “type”: “blog” } All documents which type is ‘blog’ { } Empty array

Time to perform an injection

There is no filtering on the nature of the query object here. The developer expects the value of ‘type’ in the payload to be a string.

However, nothing prevents an attacker to place an object there. Also, the attacker can also use MongoDB commands!

Payload Result { “type”: { “$gte”: “” } } All documents in the collection including the ‘secret projects’

The $gte command is compatible with strings.

One could claim that there is no much harm here. The attacker was not able to modify the content of the database. This would not be a good way of thinking:

This example shows how easy it is to massive data exfiltration from a vulnerable server. (Big players like Yahoo! know a bit about it)

Other methods that can change the content of the database exist and could be injectable. For instance:

updateMany(filter, update, options, callback)

Where:

`filter` is a query object that could be injectable

`update` is an object holding the rules regarding the modification of the documents for for which the predicate of `query` are true.

Other Gotchas

The package used by Express to parse querystring is qs. This package parses querystrings as object. Querystrings are injectable.

Some MongoDB commands executes JavaScript code within the database engine. Those are `$where`, group and map-reduce operations (see documentation here). Until MongoDB 2.4, the JavaScript code had access to the `db` object from within the query. That means that someone injecting such a command in your application could be able to do anything with the content of your database.

Conclusion

In this short article, we saw how NoSQL injections are possible. The attack method is not really complicated and there are several ways to protect an application against such injections.

Soon, we will publish an article regarding the method to prevent MongoDB injections in a Node.js application. So stay tuned!

How to protect yourself without pain?

Maintaining a data validation layer on every endpoint of an application can be very painful and time-consuming.

At Sqreen, we believe developers should be able to focus on developing their applications without having to constantly fear for security. We also believe developers deserve security tools that look like the other tools they use every day.

Sqreen will block attacks in your application (including NoSQL injections, SQL injections or XSS) without you having to take any action or to change your code. The best thing is that Sqreen takes literally 30 seconds to install in a Node.js application.

Appendix

Here are the slides of the lightning talk I gave on this topic at the DotJS conference in 2016.

About the Author

Vladimir de Turckheim is the Node.js lead engineer at Sqreen.io and was previously expert in cyber-security. He is involved in various open-source projects in JavaScript, mostly within the hapijs project.