Allow & Deny Challenge - Check Yourself

Written by Pete Corey on Jun 15, 2015.

If you read Crater, or follow the Discover Meteor blog, you probably saw Sacha Greif’s recent Allow & Deny Security Challenge. If you haven’t taken the challenge yet, go do it now! It’s a great way to flex your Meteor security muscles. Plus, it really opens your eyes about how careful you need to be when writing allow & deny rules for your Meteor collections.

I decided to have some fun with the challenge and based my implementation on Meteor’s check package. Before we dive into my solution, here’s my MeteorPad submission, and here’s the allow function in its entirety:

Messages.allow({
    update: function (userId, doc, fields, modifier) {
        var checkEdit = {
            $set: {
                body: String
            }
        };

        var checkLike = {
            $addToSet: {
                likes: Match.Where(function(likes) {
                    check(likes, String);
                    return likes == userId &&
                           !_.contains(doc.likes, userId);
                })
            },
            $inc: {
                likesCount: Match.Where(function(likesCount) {
                    check(likesCount, Number);
                    return likesCount == 1;
                })
            },
        };

        if (userId == doc.userId) { // Like or edit
            return Match.test(modifier, Match.OneOf(checkEdit, checkLike));
        }
        else { // Like
            return Match.test(modifier, checkLike);
        }
    }
});

The high level plan of attack for this allow method is to permit a user to either edit the body of their own post, or like a post. A user may only like a post once, and a user may not like and edit their post in a single update. To implement these restrictions using check, we need to think about what the modifiers for these two actions will look like.

The Edit Message Pattern

The modifier for updating your message is very simple. We’re expecting a $set on the body field. Additionally, we’re expecting body to be a String. Written as a pattern, it would look like this:

var checkEdit = {
    $set: {
        body: String
    }
};

The Like Message Pattern

The modifier for liking a message is slightly more complicated. We expect the current user’s userId to be added to the likes field using the $addToSet operator, and we expect the likesCount field to be incremented by one. We can use Match.Where to assert that the userId we’re adding to the likes field is a String, is equal to the current user’s userId, and doesn’t already exist in the array:

$addToSet: {
    likes: Match.Where(function(likes) {
        check(likes, String);
        return likes == userId &&
               !_.contains(doc.likes, userId);
    })
}

We can also use Match.Where to make sure we’re only adding 1 to likesCount:

$inc: {
    likesCount: Match.Where(function(likesCount) {
        check(likesCount, Number);
        return likesCount == 1;
    })
}

All together, the modifier we expect when liking a message should match this pattern:

var checkLike = {
    $addToSet: {
        likes: Match.Where(function(likes) {
            check(likes, String);
            return likes == userId &&
                   !_.contains(doc.likes, userId);
        })
    },
    $inc: {
        likesCount: Match.Where(function(likesCount) {
            check(likesCount, Number);
            return likesCount == 1;
        })
    },
};

Apply The Patterns

The last section of the allow method applies these patterns to the modifier we’ve been given. In my original submission, I implemented this section as two calls to check wrapped in a try/catch block. If either of the checks failed, the catch block would prevent the thrown exception from bubbling up and return a false to prevent the update. Later, I realized that I could use Match.test instead.

If the user making the update owns the document, we use Match.OneOf to allow them to either edit the message or like the message:

if (userId == doc.userId) {
    return Match.test(modifier, Match.OneOf(checkEdit, checkLike));
}

Otherwise, we only let them like the message:

else {
    return Match.test(modifier, checkLike);
}

And that’s it!

Final Thoughts

Originally, this was intended as a just-for-fun experiment, but the check approach is beginning to grow on me. The idea of using patterns to describe the modifier for each action and then applying the appropriate pattern based on the user’s permissions seems very readable and easy to understand at a glance. I might make use of this pattern in the future.

Check is a suprisingly powerful library capable of preventing a wide variety of Meteor security issues if used correctly. I highly recommend reading through the docs and brushing up on your checking skills!

Good Night 1pxsolidtomato

Written by Pete Corey on Jun 10, 2015.

Good night, 1pxsolidtomato. It’s been a fun ride.

It’s been nearly one year since I put up my first post on 1pxsolidtomato.com. Since then, I’ve transitioned into being an active member of the Meteor community, and I’ve started my own web development company orbiting the Meteor platform.

In an effort to consolodate my blog with my work, I’m putting an end to 1pxsolidtomato and moving all of my posts and articles under blog.east5th.co. Rest assured, you can continue to expect weekly posts about Meteor development and security.

This transition marks the beginning of a series of changes coming to East5th. Stay tuned for as we continue to delve into the world of Meteor!

Authentication with localStorage

Written by Pete Corey on Jun 8, 2015.

Unlike most modern web frameworks, Meteor doesn’t make use of cookies. Instead, it uses the relatively new localStorage functionality found in modern browsers. This design decision essentially makes Meteor immune to Cross Site Request Forgery (CSRF) attacks, and opens the door to exciting new authentication features not previously possible with cookies.

CSRF Proof

If Meteor were to use cookies for authentication, it would have to be done during the WebSocket handshake. The handshake is the first and only HTTP request Meteor applications make to the server and is therefore the only place to pass session cookies to the server. However, without some kind of added protection, authentication at this point would expose our Meteor applications to a particularly nasty variant of CSRF dubbed Cross Site WebSocket Hijacking, or CSWSH.

In this scenario, CSWSH could occur if a user authenticated with our application visited a malicious website that attempted to establish a DDP connection to our Meteor application:

var ddp = DDP.connect(‘http://our-application.io’);
ddp.subscribe(...);
ddp.call(...);

DDP.connect makes a GET request to our application’s WebSocket endpoint, passing along our session cookie. The GET request returns with a 101 Switching Protocols response and the WebSocket is established. WebSocket connections aren’t protected by modern browsers’ same-origin policy, so the browser happily establishes the DDP connection. The malicious site is now free to view, modify, and delete all of your user’s data without their knowledge or consent. Uh oh!

localStorage To The Rescue

Rather than using cookies and implementing complicated countermeasures against CSRF attacks, Meteor opts for a more elegant solution and stores session tokens in localStorage. localStorage, unlike cookie data, is accessed via JavaScript and is only accessible by the domain it belongs to. Unlike cookies, they are not sent along with HTTP requests. This means there is no chance of a third party website directly or indirectly accessing and using our users’ session tokens.

Reactive Authentication

Using localStorage as our authentication mechanism also lets us do cool things like reactive authentication. Imagine a user with your web application loaded on two different tabs. If that user were to log in to your application on one tab, they would instantly be logged in on the other tab. Similarly, logging out of the application in one tab also logs the user out in the second tab. Meteor accomplishes this by listening for storage events and reactively updating the client’s authentication state. These storage events also open the door for more exciting authentication functionality, like sharing authentication state across multiple applications.