Interactive Meteor Security Demonstration

Meteor comes installed with two potentially harmful packages, autopublish and insecure.

The first doesn’t sound so bad, but insecure? That must have been left in accidentally by the developers, right? Actually, it’s meant to (and does) significantly decrease the prototyping time necessary to get an app up and running. Without going into too much detail, autopublish automatically publishes (duh) the contents of your database to the client, meaning anyone can see the details of your database simply by using the JavaScript console in their browser, and insecure allows anyone to write to (insert, update, remove) the contents of your database through the client. Obviously they are much more complex but the important thing to note is that both packages are extremely helpful when testing an app on your own machine but should absolutely be removed before deployment.

Now I am by no means a Meteor expert, especially when it comes to security, so the point of this post will not be to educate you on the best practices or explain all the technical aspects, as there are plenty of articles out there already (see Resources below), but to do something that I haven’t really seen anyone do, let you actually see the insecurities and fuck with a database yourself…for science!

I’ll give you links, one (relatively) secure (dmstodos.meteor.com), and one insecure (dmstodosinsecure.meteor.com) version of the same, simple Meteor Todo application (from eventedmind.com). I’ll then walk you through some of the major flaws in the insecure version and how to prevent them in the first app. I am assuming you have prior exposure to MongoDB commands but if not, don’t worry, I’ll tell you exactly what to type.

Before we get straight into it, it is necessary for you to have a (very) basic understanding of the database structure in a Meteor app. Again, the simple version is that the developer creates a collection where they store the documents and the attributes linked to those documents. For this example the collection is called “Todos”, the documents are each todo item, and the attributes are “subject” (text of the todo), “is_done” (a boolean declaring if the user has checked the task complete or not), “user_id” (the unique id of the user that created the todo), and “_id” (an automatically generated id for the todo). That’s about all you need to know about structure, I’ll explain each command as we reach them but check the links in the Resources section to learn more about MongoDB.

Autopublish

Let’s get started by opening the Console on the insecure app (Ctrl+Shift+I in Chrome). Once open, type:

Todos.find().fetch()

and hit enter. You should see a bunch of arrows with the word Object next to them. If you click on the arrow next to each of the Objects, you’ll see the specific attributes (_id, created_at, is_done, subject, and user_id) with the plain text values of those attributes.

Something like this

The content of our Todos collection visible to the Client

This is autopublish at work, allowing the client (your web browser) to access a mirror of the database on the server. If you were to run the same find/fetch command on the secure app, you would see an empty array ([ ]) meaning that the client cannot read the database. For obvious reasons, this would cause problems with a large scale commercial product but for a small app like this, it’s not really that big of a deal. Still, let’s learn how to fix it!

The Fix

First off, remove autopublish from your project by typing

meteor remove autopublish

in the project directory. Now that the server is not automatically publishing the content of the database to the client, we need to set up our own Publish/Subscribe methods. We can do this by assigning

Meteor.publish('todo', function() {

return Todos.find({user_id: this.userId});
});

either in the JavaScript file located in the server folder, or inside of a

if(Meteor.isServer) {
}

block, and

Meteor.subscribe('todo');

inside of

if(Meteor.isClient) {
}

or the JavaScript file in the client folder. Essentially we are setting what the server is allowed to publish to the client and then we tell the client to subscribe to whatever the server is sending out. If we were to return everything from the collection i.e.

Meteor.publish('todo', function() {

return Todos.find({});
});

it would be the same as leaving autopublish on. Instead our code filters out the results to only show the ones created by the user currently logged in. Now, if we attempt the same find/fetch without being logged in, we see the same empty array from the secure app!

Insecure

Now for some real fun, go ahead and type in

Todos.insert({subject: “Secure yo app!”})

You should see a new todo pop up even though you can’t type anything in the text box. This is due to Meteor’s wonderfully dynamic interface updating in response to a change in the database — a change you made!

Illegally inserting our own content to the collection

We can execute any number of commands with insecure installed, we could insert a todo in someone else’s list,

Todos.insert({subject: “Secure yo list!”, user_id: “gRHh6HLsLnwtgZGhf”})

we could update someone’s todos so that they all say something ridiculous,

Todos.update({_id: “TTFNTJ2Ddcg7cRdcF”},{$set:{subject: “Eat smelly socks!”}})

we could even remove someone’s todos

Todos.remove({_id: “TTFNTJ2Ddcg7cRdcF”})

This is the danger of leaving the insecure package when you deploy your projects. All someone needs is the name of your collection and they could wreak havoc on your app.

The Fix

First thing’s first, remove that shit from your project when you’re ready to deploy:

meteor remove insecure

Next, there are two approaches you can take: allow/deny and methods. I’ll let you learn the difference between the two on your own but basically, allow sets what commands are okay to be executed by the client and when (if there are certain needs to be met) and methods simply move all the database interaction to the server where the client can safely call functions. I used methods because I had used them before in other projects but the example from EventedMind made use of allow so it is really up to you.

If you are using methods however, first you need to identify any collection command (insert, update, remove) in your client side JavaScript and create a function for them in the server file. E.g.

Meteor.methods({
'insertTodo': function(subject) {
var currentUserId = Meteor.userId();
Todos.insert({
subject: subject,
created_at: new Date,
is_done: false,
user_id: currentUserId
});
},


'checkTodo': function (id, isDone) {
Todos.update({_id: id}, {$set: {is_done: isDone}});
}

});

Here, I declare the methods in the server section and then I call them with parameters from the client like so

Meteor.call('insertTodo', subject);
Meteor.call('checkTodo', id, isDone);

(subject, id, and isDone are all variables declared earlier)

This ensures that all the potentially dangerous actions are performed on the server. Now when we try to insert into the collection, we get the message

insert failed: Access denied

Our app is now secure from all the h4x0rs.

Tough luck kid

Conclusion

Again, the point of this post was not to educate you too much (as I suggest you read what people much smarter than me have to say on the links below), but rather to give you a real sense of the insecurities that come with the autopublish and insecure packages. That being said, development would take a lot longer and be a lot more tedious process had the developers at Meteor not included these packages. Maybe they could include a warning when you try to deploy with the packages still installed although, they did name it insecure for fuck’s sake, how much clearer can they be? I hope you learned something from reading this post or at least had fun screwing with an insecure web app. Either way, follow me on twitter for more nonsense.

Resources