Renaming Your Way to Admin Rights

Below you'll find a sample chapter from Secure Meteor; a guide to help you learn the ins and outs of securing your Meteor application from a Meteor security professional. If you like what you read and you're interested in securing your Meteor application, be sure to read the entire book!

Learning from our previous mistakes, but also feeling especially clever, we came up with a new solution to our profile modification problem that minimized work for ourselves, and maximized the customizability of our users’ profiles.

Our new implementation of updateProfile accepts a database modifier from the client, checks that it’s an appropriate update to apply, and then applies it to the current user document:

Our checkEdit function loops over every field of every operation in our edit object, and checks that the field being edited is listed as editable by our current user’s role in our userSchema:

Our userSchema has an editableBy field attached to every field on our user document. Many fields are freely editable by all roles, but certain fields, like the isAdmin field is only editable by users with the admin role:

As an example, a user with the user role could execute the following call to editProfile:

But that same user could not modify the isAdmin field of their user document because they lack the admin role:

Our system holds up to most kinds of trickery as well. A user might try to increment ($inc) their isAdmin flag, but this is still recognized as an operation against the isAdmin field by a non-admin user, and is prevented.

Unfortunately, our solution is making a fatal assumption. We’re assuming that all MongoDB database updates only affect their source field. That is, all of our operations follow the same pattern:

We’re assuming that a will always be the field being updated by the operation, never b. There’s a single MongoDB update operation that violates this assumption.

Imagine if a malicious user with the user role were to execute the following update to their profile:

They run a multipart edit that first sets their shareOnlineStatus flag to true. This is fine; this field is editable by normal users. Next, they run a $rename operation to rename the shareOnlineStatus field to isAdmin. This update is allowed because it’s technically operating on the shareOnlineStatus field, which the user has permission to edit, but the end result is that their user document now has the isAdmin flag set to true. Our malicious user has successfully made themselves an administrator.

We were assuming that users would only ever perform unidirectional updates, like $set, $inc, and $push to their user document. This assumption cost us dearly. Who knows what kind of havoc our malicious-user-turned-administator can wreak on our application and our users.

The fix to this vulnerability, like the fix to every other vulnerability, is to turn our assumptions into assertions. We really only ever want our users passing us $set update operators in their edit update objects. Let’s make that assumption check with a call to check:

Here we’re checking that edit is an object with a single field, $set. We’re also asserting that the value of $set must be an Object. Usually, I caution against this type of incomplete checking, but our subsequent call to checkEdit thoroughly checks the contents of $set for us.

Now, a malicious user trying to pass anything other than a $set operation will be rejected and our application and its users remain safe and sound.