If you’ve read my previous posts, you’ll know that I talk quite a bit about the dangers of not checking your method and publication arguments. These posts usually boil down to the dangers of letting users pass arbitrary data into your collection query objects. These types of vulnerabilities usually take the form of data leakage, or unauthorized data modifications (which are very serious issues), but it’s also possible to completely hang an application with a well-crafted query.
Let’s dig into an example to see how this can happen and what we can do to prevent it!
The Setup
Let’s pretend that you’re building a Meteor application. Within this application you have a very simple method called grabData
. It expects you to pass in an ID, and it will return the corresponding item from the Data
collection:
Meteor.methods({
grabData: function(id) {
return Data.findOne(id);
}
});
Awesome, our method works! Now imagine what would happen if a malicious user ran the following method call in their browser console:
Meteor.call('grabData', {$where: "d = new Date; do {c = new Date;} while (c - d < 10000);"});
Uh oh… What is this $where operator? What’s all this Date
and looping business? And why is my app completely unresponsive?
The Exploit
The $where
operator will execute any valid Javascript string on each element in the collection over which you’re running your query. It’s intended use is to run complicated queries that may carry out some business logic on a document before deciding whether to include it in the result set. Unfortunately, $where
will execute any Javascript passed into it - including Javascript designed to loop forever.
We were expecting the client to pass an ID into grabData
, but our malicious user decided to get more creative. They passed in a selector object with a $where
operator designed to spin your Mongo instance’s CPU at 100% for 10 seconds. By pegging the CPU of your Mongo instance (which may or may not be the same CPU used by your Meteor application), the malicious user has essentially DOS’d your application.
Check out a quick demonstration:
This is a particularly nasty vulnerability. In this case, our malicious user was kind enough to free the CPU after 10 seconds. In the real world, an attacker may peg your CPU indefinitely, forcing you to restart your Mongo instance.
The Fix
So how do we avoid this type of “NoSQL injection”? Wherever possible, don’t trust user input, and definitely don’t pass it directly into a collection query. Always check that your user arguments match your expectations, and be especially careful when using the $where
operator.
A simple fix to our grabData
method may look like this:
Meteor.methods({
grabData: function(id) {
check(id, String);
return Data.findOne(id);
}
});
You can use my new package, east5th:check-checker to see if you have any unchecked arguments in your application’s publications and methods.