In the past, I’ve seen the multi parameter on MongoDB updates as an annoying inconvenience. Without fail, I’ll forget to add the flag when it’s needed, and waste a frustrating amount of time trying to deduce why my update isn’t behaving as expected. But, a recent trek through Telescope’s codebase revealed to me just how valuable it can be to default to updating a single item at a time. Come with me on a journey…

On a side note, Telescope is one of the highest quality open source Meteor projects I've seen to date. I highly recommend it as a platform!

Digging Into Telescope

The code that opened my proverbial eyes is the changeEmail method found in /server/users.js in Telescope. Take a look:

changeEmail: function (userId, newEmail) {
  var user = Meteor.users.findOne(userId);
  if (can.edit(Meteor.user(), user) !== true) {
    throw new Meteor.Error("Permission denied");
  }
  Meteor.users.update(
    userId,
    {$set: {
        emails: [{address: newEmail, verified: false}],
        email_hash: Gravatar.hash(newEmail),
        "profile.email": newEmail
      }
    }
  );
}

If you’ve read my previous posts, I hope you’ll immediately notice that userId and newEmail are not being checked. Can that be exploited? What happens if a malicious user, Mallory, decides to pass in a carefully crafted object as the userId?

Meteor.call(‘changeEmail', {_id: {$gte: Meteor.userId()}}, ‘mallory@is.evil');

This userId object, when used in a Mongo query, will return all users with IDs ordinally greater than or equal to the current user’s ID. We can roughly assume that this is about half of the users in the system. The list of returned users will always return the current user first, due to the index on the _id field.

In our changeEmail method, Meteor.users.findOne will happily accept our userId object and treat it as a query object. It will grab the first result of this query, which happens to be the current user (Mallory).

Next, the method checks if the current user has permission to edit the user found. Because the first user found is the current user, permission is granted. Proceed with the update!

You Get a New Email Address! And You Get a New Email Address!

If Mongo didn’t require explicitly setting the multi parameter, calling Meteor.users.update with our malicious userId object would result in a huge chunk of users having their emails changed to Mallory’s email address. This would be a Very Bad Thing™. He could easily reset their passwords through normal channels and take over their accounts!

But Not Really…

Thankfully, Mongo defaults to updating only a single object unless the multi flag is explicitly set to true. In this example, only the first result of the query, the current user, will be updated with the new email address. This means that the method functions as intended, and there is no vulnerability! Three cheers for the multi flag!

Final Thoughts

While I’m sure I’ll still continue to forget to add the multi flag when writing updates, I now have a newfound respect for this aspect of Mongo. This conservative default, which was initially built in with performance in mind, also acts as a protective mechanism to prevent us from inadvertently modifying more data than we intended.

Lastly, remember to always check your method and publication arguments! While it ended up not being a problem in this example, we nearly had a serious vulnerability on our hands. It’s always better to be safe than sorry.