Why I Can't Wait For ES6 Proxies

Written by Pete Corey on Nov 9, 2015.

Full ES6 support is just around the corner. In fact, nearly all of ES6 is available to us through compilers like Babel that transpile ES6 syntax into ES5 code. Unfortunately, one of the ES6 features I’m most excited about can’t be implemented in ES5. What feature is that? Proxies, of course!

Proxies make some incredibly exciting things possible. Imagine a Meteor method like the one below:

Meteor.methods({
  foo: function(bar) {
    return Bars.remove(bar._id);
  }
});

As I’ve talked about in the past, this method exposes our application to a serious security vulnerability. A user can pass in an arbitrary MongoDB query object in the _id field of bar like this:

Meteor.call("foo", {_id: {$gte: ""}});

This would delete all of the documents from our Bars collection. Uh oh! Imagine if we could automatically detect and prevent that from happening, and instead throw an exception that tells the client:

Meteor.Error: Tried to access unsafe field: _id

Our _id field would be accessible only after we check it:

Meteor.methods({
  foo: function(bar) {
    check(bar, {
      _id: String
    });
    return Bars.remove(bar._id);
  }
});

Any attempts to access a field on a user-provided object will throw an exception unless it’s been explicitly checked for safety. If this were possible, it could be used to prevent entire categories of security vulnerabilities!

With proxies, we can make this happen.

What is a Proxy?

An ES6 Proxy is basically a middleman between an object, and the code trying to access that object. When we wrap an object with a proxy, we can oversee (and interfere with) every action taken on that object.

Proxies do this overseeing through “traps”. A trap is just a callback that’s called whenever a certain action is taken on the proxy object. For example, a get trap is triggered any time a piece of code tries to get the value of a field on the proxy. Likewise, a set trap is triggered any time you try to set the value of a field.

In the above example, our proxy sees that we’re trying to access _id on the bar object, but because it knows that check hasn’t been called on that field yet, it throws an exception. If we had checked the field, the proxy would have let _id’s value pass through.

A rough sketch of this kind of proxy would look something like this:

CheckProxy = {
  get: function(target, field) {
    if (!target ||
        !target.__checked ||
        !target.__checked[field]) {
      throw new Error("Tried to access unsafe field: " + field);
    }
    return target[field];
  }
};

But how does the proxy know when a field has been checked? We have to explicitly tell the proxy that each field has been checked after we’ve determined that it’s safe to use. One way to do this is through a custom set trap:

CheckProxy = {
  ...
  set: function(target, field, value) {
    if (field == "__checked") {
      if (!target.__checked) {
        target.__checked = {};
      }
      target.__checked[value] = true;
    }
    else {
      target[field] = value;
    }
    return true;
  }
};

If we wanted to use our proxy as-is, there would be a good amount of manual work involved. We’d have to instantiate a new proxy object for each one of our object arguments, and then explicitly notify the proxy after each check:

Meteor.methods({
  foo: function(bar) {
    bar = new Proxy(bar, CheckProxy);
    check(bar, {
      _id: String
    });
    bar.__checked = "_id";
    return Bars.remove(bar._id);
  }
});

This is too much work! It wouldn’t take long to lose diligence and fall back to not checking arguments at all.

Thankfully, we can hide all of this manual work through the magic of monkey patching.

The first thing we’ll do is patch our check method to tell our proxy whenever we check a field on an object:

_check = check;
check = function(object, fields) {
  if (object instanceof Object) {
    Object.keys(fields).forEach(function(field) {
      object.__checked = field;
    });
  }
  _check.apply(this, arguments);
};

Next, we just have to patch Meteor.methods to automatically wrap each Object argument in a proxy:

_methods = Meteor.methods;
Meteor.methods = function(methods) {
  _.each(methods, function(method, name, obj) {
    obj[name] = function() {
      _.each(arguments, function(value, key, obj) {
        if (value instanceof Object) {
          obj[key] = new Proxy(value, CheckProxy);
        }
        else {
          obj[key] = value;
        }
      });
      method.apply(this, arguments);
    };
  });
  _methods.apply(this, arguments);
};

Whew, this is getting dense!

Thankfully, that’s all the patching we have to do. Now, we can revert back to our original method and still reap all of the benefits of automatic check enforcement for all object fields throughout all of our Meteor methods.

Shortcomings

ES6 Proxies are currently only supported in Firefox, which means that what I described above currently isn’t possible. Until proxy support comes to V8, Node.js, and finally Meteor, all we can do is wait and dream.

The implementation I described here is fairly unsophisticated. It only works when accessing fields within the first layer of an object. It also pollutes the provided object with a __checked field, which may wreak inadvertent havoc. In future versions of this idea, both of these issues could easily be solved.

I hope this post has given you a taste of the awesome power of proxies. Fire up your Firefox console and start experimenting!

Meteor Space Camp

Written by Pete Corey on Nov 2, 2015.

Late last month I had the chance to fulfill a childhood dream; I went to Space Camp! No, not that kind of space camp… I went to Meteor Space Camp!

Thanks to the hard work of Josh Owens and a handful of very generous sponsors and organizers, I had the opportunity to spend a weekend with fifty other passionate Meteorites in a beautiful cabin nestled in the Great Smoky Mountains.

When it came time for talks, I used the opportunity to discuss something near and dear to my heart - software security! I gave a quick presentation on the importance of always checking your arguments in your Meteor applications. We looked at example methods, publications, and collection validators and dove into how they could be exploited by malicious users. I capped things off with a quick demo (that didn’t completely go as planned) to make things more real.

I’ll be sure to post a video of the talk once it’s available, but in the meantime check out the slides for a quick teaser.

Being able to put faces to people I’ve met in the community was an invaluable experience. I feel like we solidified some real friendships, and made great new connections with awesome people in the community. The value of meeting people face to face and talking about something you love really can’t be underrated. If you didn’t make it out to Space Camp this year, read Katie Reed’s fantastic write-up to get a taste of what you missed.

Rename Your Way To Admin Rights

Written by Pete Corey on Oct 19, 2015.

MongoDB modifier objects are hard. Incredibly hard. When you’re dealing with almost two dozen different update operators, it’s difficult to imagine all the ways in which a piece of data can be changed.

A few months ago I found an interesting issue in Telescope that perfectly highlights this problem. Telescope’s method to complete a user’s profile wasn’t correctly validating the MongoDB modifier being passed in. Exploiting that, I was able to pass in an underhanded modifier and give myself instant admin access.

Don't mistake this post as a warning against using Telescope; the vulnerability I discuss here was immediately patched after it was reported. The Telescope project continuously impresses me with its architectural choices and its obvious focus on code quality and understandability!

Security In Telescope

Telescope is an interesting project. While I constantly talk about how you should rigorously check all of your method and publication arguments, Telescope does did very little of this - at least at the time I discovered this vulnerability. Contrary to what I might have you believe, this didn’t cause the world to end. In fact, I had trouble finding any security issues at all in the project.

How is this possible? Surely without checking arguments, vulnerabilities abound!

Telescope achieved security through its heavy use of what Sacha Greif, Telescope’s creator, calls Query Constructors. Instead of directly passing user input into query and modifier objects, Telescope uses that user data to guide the construction of new query objects. User input is only injected into these new objects when absolutely necessary, and in those cases it’s thoroughly and explicitly sanitized.

Digging Into Validation

Despite this hardened architectural approach, there was one piece of code that caught my eye while digging through Telescope’s source. The completeUserProfile method was taking in a modifier object from the client and, after validation, passing it directly into a call to Users.update.

The validation process seemed straight-forward. Each field in the users schema maintained a list of roles allowed to modify that field. The completeUserProfile method looped over each field being modified and checked that the user had the required role:

// go over each field and throw an error if it's not editable
// loop over each operation ($set, $unset, etc.)
_.each(modifier, function (operation) {
  // loop over each property being operated on
  _.keys(operation).forEach(function (fieldName) {
    var field = schema[fieldName];
    if (!Users.can.editField(user, field, user)) {
      throw new Meteor.Error("disallowed_property", ...);
    }
  });
});

So, users with "member" or "admin" roles could modify telescope.displayName, but only users with the "admin" role could modify isAdmin:

displayName: {
  ...
  editableBy: ["member", "admin"]
}
isAdmin: {
  ...
  editableBy: ["admin"]
}

$Renaming For Fun And Profit

But $set and $unset aren’t the only update operators at our disposal. The validation rules described above mean that users with the "member" role could run any update operator on displayName.

What would happen if I $rename displayName to isAdmin? Let’s try it!

Meteor.call("completeUserProfile", {
  $rename: {
    "telescope.displayName": "isAdmin"
  }
}, Meteor.userId());

Instantly, various admin controls appear in our browser (isn’t reactivity cool?)! And just like that, we gave ourself admin permissions.

So, what’s going on here?

Let’s assume we had a value in displayName; let’s say it was "YouBetcha". In that case, our user document would look something like this:

{
  ...
  isAdmin: false,
  telescope: {
    ...
    displayName: "YouBetcha"
  }
}

By running an update on our user document that renames telescope.displayName to isAdmin, I’m effectively dumping the value of "YouBetcha" into isAdmin. My user document would now look something like this:

{
  ...
  isAdmin: "YouBetcha",
  telescope: {
    ...
  }
}

Interestingly, SimpleSchema does not enforce type constraints during $rename, so we can happily dump our String into the Boolean isAdmin field.

Most of the admin checks throughout Telescope were checks against the truthiness of isAdmin, rather than strict checks (user.isAdmin === true), or checks against the users’ roles, so our isAdmin value of "YouBetcha" gives us admin access throughout the system!

The Fix & Final Thoughts

After reporting this fix, Sacha immediately fixed this issue in the v0.21.1 release of Telescope.

His first fix was to disallow $rename across the board, just like Meteor does in updates originating from the client. Later, he went on to check that the modifiers being used are either $set or $unset.


MongoDB modifier objects can be very difficult to work with, especially in the context of security. You may be preventing $set updates against certain fields, but are you also preventing $inc updates, or even $bin updates? Are you disallowing $push, but forgetting $addToSet? Are you appropriately handling $rename when dealing with raw modifier objects?

All of these things need to be taken into consideration when writing collection validators, or accepting modifier object from clients in your Meteor methods. It’s often a better solution to whitelist the modifiers you expect, and disallow the rest.


Are you using a vulnerable version of Telescope? Use my Package Scan tool to find out. You can also include Package Scan as part of your build process by adding east5th:package-scan to your Meteor project:

meteor add east5th:package-scan