The entire premise behind my latest project, Inject Detect, is that NoSQL Injection attacks can be detected in real-time as they’re being carried out against your application.
But how?
In this article, I’ll break down the strategy I’m using for detecting NoSQL Injection in MongoDB-based web applications.
At a high level, the idea is to build up a set of expected queries an application is known to make, and to use that set to detect unexpected queries that might be to result of a NoSQL Injection attack.
Let’s dig into the details.
Expected Queries
Every web application has a finite number of queries it can be expected to make throughout the course of its life.
For example, a shopping cart application might query for single orders by _id
:
Orders.findOne(orderId);
Similarly, it might query for a number of orders created in the past three days:
Orders.find({createdAt: {$gte: moment().subtract(3, "days").toDate()}});
These queries aren’t limited to fetching data. When a user “deletes” an order, the application may want to set a flag on the order in question:
Orders.update({userId: this.userId}, {$set: {deleted: true}});
Each of these individual queries can be generalized based on the shape of the query and the type of data passed into it.
For example, we expect the Orders.findOne
query to always be called with a String. Similarly, we expect the Orders.find
query to be passed a Date for the createdAt
comparison. Lastly, the Orders.update
query should always be passed a String as the userId
.
Orders.findOne({_id: String});
Orders.find({createdAt: {$gte: Date}});
Orders.update({userId: String}, ...);
An application might make thousands of queries per day, but each query will match one of these three generalized query patterns.
Unexpected Queries
If our application makes a query that does not fall into this set of expected queries, we’re faced with one of two possibilities:
- We left a query out of our set of expected queries.
- Our application is vulnerable to a NoSQL Injection vulnerability.
Imagine our application makes the following query:
Orders.findOne({_id: { $gte: "" }});
A query of this pattern (Orders.findOne({_id: {$gte: String}})
) doesn’t exist in our set of expected queries. This means that this is either an expected query that we missed, or our application is being exploited.
It’s unlikely that our application is trying to find a single Order
who’s _id
is greater than or equal to an empty string.
In this case, it’s far more likely that someone is exploiting our Orders.findOne({_id: String})
query and passing in an orderId
containing a MongoDB query operator ({$gte: ""}
) rather than an expected String.
We’ve detected NoSL Injection!
By watching for queries that fall outside our set of expected queries, we can detect NoSQL Injection as it happens.
Similar Expected Queries
Basing our NoSQL Injection detection strategy around expected and unexpected queries has an added bonus.
Because we have a set of all expected queries for a given application, unexpected queries that are the result of NoSQL Injection attacks can often be linked back to the query being exploited.
To illustrate, in our previous example we detected an unexpected query against our application:
Orders.findOne({_id: {$gte: ""}});
Inspecting our set of expected queries, it’s obvious that the most similar expected query is the Orders.findOne
query:
Orders.findOne({_id: String});
As the application owner, we know that we need to enforce more stringent checks on the type of the provided orderId
.
Based on this information, an application owner or developer can deduce which specific query is being exploited within their application and respond quickly with a fix.
Final Thoughts
Progress on Inject Detect continues to move along at a steady pace. Once finished, Inject Detect will automatically apply this type of real-time NoSQL Injection detection to every query made by your Meteor application.
If you’re interested in learning more, be sure to sign up for the Inject Detect newsletter.