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.

Keep It Secret, Keep It Safe

Written by Pete Corey on May 25, 2015.

It’s fairly well established that you shouldn’t be storing your application’s deployment-specific configuration options directly in your source code. Keeping secrets in your code unnecessarily expands your application’s circle of trust. But did you know that by keeping secrets in your code you may inadvertently be leaking them to your clients?

The Setup

Let’s pretend that we have a Meteor method that’s called whenever a user purchases something in our application. Knowing that we want to leverage latency compensation, we define this method in a shared location so both the client and server have access to it. In a server block, the method adds a transaction to our payment processing system. In order to add this payment, we need to pass along a secret key associated with our application to verify that we authorize the transaction.

Take a look at the method:

Meteor.methods({
    purchase: function(item) {
        // checks and validation
        ...
        if (Meteor.isServer) {
            Payments.add(..., 'XYZ-SSECRET-KEY');
        }
    }
});

It’s very important that we keep our secret key a secret! If anyone other than our server has access to our key, they would be able to add payments on our behalf.

The Problem

Unfortunately, in this scenario, our secret key is not being kept a secret. To grab our key, a malicious user would simply need to open their browser console anywhere in our application and grab the purchase method’s source:

Meteor.connection._methodHandlers.purchase.toString();

"... 'XYZ-SSECRET-KEY' ..."

Our fundamental error here is assuming that our Meteor.isServer guard prevents code from being shipped to the client. This isn’t always true! When a method is defined in a location that is visible to both the client and the server, it’s entire handler function is passed to the client, server-only code and all.

Check out my post on black box auditing Meteor methods to get a better understanding of what code is made visible to the client.

The Solution

The quickest solution to this problem is to move our secret key out of our code and into our settings file:

{
    "payment_secret": "XYZ-SSECRET-KEY"
}

Our updated purchase method would look like this:

Meteor.methods({
    purchase: function(item) {
        // checks and validation
        if (Meteor.isServer) {
            Payments.add(Meteor.settings.payment_secret);
        }
    }
});

From a client/server perspective, nothing has changed. Our Meteor.isServer block is still being sent to the client. The fundamental difference with this approach is that Meteor.settings.payment_settings is not available on the client. Even if a malicious user digs into the method’s source on the client, they won’t get to our secret key.