Episode 03: Security
In this episode of the Discover Meteor Podcast: how to make your Meteor apps more secure.
- 00:36: Misunderstandings and misconceptions
- 02:09: Insecure defaults
- 04:30: Publication security
- 09:56: Operations security
- 15:58: Other security concerns
- 17:37: Security resources
- Josh Owens: Meteor Security 101
- Josh Owens: Meteor Security 201
- Pete Corey: East 5th
Setting The Record Straight
Sacha: Today, we’ll talk about security for Meteor apps. Security is important for any kind of Web app. But for Meteor apps, especially, there are a lot of misunderstandings and misconceptions floating around.
Tom: One of those seems to be a historical artifact as a fact that when Meteor first launched, by accident. I don’t think that they were quite ready to go live, though. They took the initiative and went big on “Hacker News.” At that point in time, Meteor had no security, and I think that conception has hung about with the framework. But it’s really far from true these days.
Sacha: It’s also related to the fact that Meteor just works differently from the traditional server-client model of Web apps. Because in a real PHP Web app, your data stays on the server. The only data that you’re exposing is whatever you send across the wire to the client, as an HTML page, basically.
Tom: In Meteor, as we know, we’ve got miniMongo on the client which is essentially a copy of the database. If you’re doing things right, it’s a subset of real database, and it’s a secure subset. It’s only the documents that the current user needs to see, to do what they’re doing.
It is scary when you think about it like that, because all we need to do is make one mistake, and it’s no longer the right subset of the real database. It means you need to think about security differently, it doesn’t mean Meteor is less secure. It does, I guess, set off red flags with people who are used to seeing things in a certain way.
Sacha: We should also mention that Meteor does have insecure defaults through the autopublish and insecure package that get included with every new Meteor app you create. What the autopublish package does, it basically takes your whole database and copies it over to the client.
Tom: Insecure similarly lets any user make any write that they like to the database, so it’s really the most insecure possible world that they give you by default.
Sacha: But to be fair, autopublish and insecure are meant to help speed up the prototyping phase of your app development process. They’re not meant to be used in production, obviously. You’re supposed to remove both of them at some point.
Tom: You are. Frameworks have been burned in the past by insecure defaults. There is a famous case of Ruby on Rails allowing updates to any parameter, and that burnt some pretty big companies. It was a much more subtle insecure by default.
You don’t have to work on your project for very long before you realize that perhaps you should have insecure and autopublish turned off. But, still, you do wonder whether, perhaps, Meteor should take the lesson there which is, don’t assume that users are going to figure out that this is insecure and they should be doing something differently. And make them explicitly opt in to insecure defaults.
Sacha: For their defense, they did call it, literally, insecure.
Sacha: So that should be a pretty big red flag.
Tom: I take the point, but my opinion, also, is it is a bit of a false argument. Because you’re not really thinking very much about you haven’t done that. You’re never going to launch a production app without realizing that you’ve got autopublish turned on.
Or if you do, you’re probably going to have other problems. Because you haven’t really been thinking very hard about what your app’s doing.
Sacha: Let’s assume that we do remove both autopublish and insecure. Beyond that, I think we can split security in two main areas which would be read security, in other words, publications and write security. In other words, operations like insert, update, remove and so on.
Tom: The first point, publication security, is really, “Well, what happens when we turn off autopublish?” Now we have no data on the client. It’s obviously the most secure situation possible, but it’s not very helpful. How do we write publications in such a way that we know that they’re safe?
Sacha: It might be helpful to just go over what we mean by publishing and subscribing. The example I like to take is a bookstore. The bookstore would be the server, and any customer can come in, get some books and bring them back home which would be the client, the browser.
Autopublish would be just buying all the books in the store. But, obviously, that would be a bit too much. So you can subscribe to only acquire a subset of those books. What we mean by publication security will be the bookstore defining that certain books are not for sale. Some of these books, they remain in the backroom. They’re not public.
A common example would be, let’s say that you only want to publish documents belonging to the currently logged-in user. Then instead of returning the whole collection in your publication, you would return a cursor and do a find based on the user ID, for example.
Tom: A further optimization you can do beyond purely picking which documents to publish, is you can also pick which fields of the document you want to publish. An important example of this is on the user’s…We don’t want to be publishing the private login information such as tokens and encrypted hashes of their password to other users. Or even probably to themselves.
In that case, we use the field specifier in a publication to make sure that we only publish the fields that we actually need. This is a good idea, anyway, outside of security. We don’t want to send down more data than we need to the client. But it’s definitely something to look out from a security perspective, and it’s often overlooked.
“Am I actually sending more data on this document than this user should really know about?”
Sacha: You can do that by passing a field specifier as part of the options to the find call in your publication. What that is is basically a list of either the fields that you do want to publish, in which case, you mark them with one, or a list of the fields that you don’t want to publish. In which case, you mark them with zero.
Another thing. We just talked about which documents can be published and which fields of these documents can be published. You also want to pay attention to how many documents can be published.
Tom: This is an easy mistake to make. You might make a publication for a pagination that has a limit in it, and allow the client to pass any limit in.
Sacha: Why is that a problem?
Tom: That’s a problem because, although you may write perfect client-side code that only ever passes in the correct number, a malicious user could go and play. It’s very easy to play on the browser console and play with publications.
A malicious user might try some different arguments. They might think to themselves, “Hmm, I wonder what happens if I put a million in here.” It’d be quite easy to instrument a denial-of-service attack on your server, if you have those kind of publications available.
Sacha: Basically, if your publication is taking a limit from the client as an argument, just adding a small if condition in there that says, “If the limit is larger than x, then set it to x.” This actually brings up another very important point, which is, don’t trust the client.
It’s important to remember that while your own application might pass correct arguments to your publication, and stay within the boundaries that you’ve defined, there’s nothing preventing a random end-user from calling the publication with their own arguments.
For that reason, you always want to assume the worst, and you always want to make sure that the arguments you’re receiving are correct and safe. The best way to do this is to always use Meteor’s check function to check your argument’s type. If you’re expecting a string, make sure it’s a string and not an object or something else.
Tom: You can use the audit argument to check package to make sure that you’re always doing this, and it will tell you if you’re not.
Sacha: Another important thing to watch out for. For example, let’s say that you want to limit the publication to only publishing a user’s own documents. Or maybe, only publishing documents to admins. Make sure that when you check the user’s ID, you use this .userId and not a user ID coming from the client.
Because, again, that can be faked very easily.
Tom: One edge case around this that people probably don’t think about too much is, keep in mind that publications aren’t reactive in the way the client-side code is. If you do something like look the user up and check a field on the user, to see if the user has permission to read some documents, and then later that field changes, the publication won’t rerun.
Usually, that’s fine, but it is something to be aware of if you’re relying on that behavior driving security in your app. If you want to change user’s permissions dynamically.
Sacha: We’ve covered publication security. The next step is write security, so MongoDB operations which can be inserting, updating or removing doc. There are two ways to deal with that. The first way is Allow & Deny, and then the second way is Meteor methods.
Tom: Allow & Deny is a simple way of controlling what a user on the client side or client-side code can do directly to your database. It’s cool thing about Meteor is that you can write in the browser operations and directly update data in your database. That might sound a bit scary to you, but that’s where Allow & Deny will help.
Sacha: The way Allow & Deny works is that, let’s focus on Allow first. You would define three Allow functions, one for each operation. Again, insert, update and remove. Each function can either return true or false. If it returns true, the operation is permitted. If it returns false, it’s not.
You don’t need to specify all three, you can just specify only insert or only update, if you only need that operation. You can also specify more than one Allow callback for each operation.
Tom: You can also specify Deny operations, which basically bans certain types of updates. You can deny a certain insert or a certain update or a certain remove. And you can have more than one Deny function per collection.
Sacha: What happens if you have more than one? For Allow, you only need one Allow callback to return true. If you have four of them, one returning true is enough for the operation to succeed.
Tom: Deny works the opposite way. You just need all of them to return false, for the operation to go through.
Sacha: Of course, you can combine Allow & Deny. You can have one or two Allow callbacks and one or two Deny callbacks, then make both of them work together.
Tom: What sort of things do you typically do in an Allow & Deny? The simplest thing you can do is just check, are you allowed to edit this particular document? In almost any Web application, certain documents belong to certain people. Usually, what you’ll do is have a user ID on the document that says, “This particular user can edit it.”
Sacha: Checking if a user owns a document is great, but you can’t really stop there. A very common mistake is forgetting to check which properties of the document are being edited. For example, if you check that I own a post on the site, and let me edit anything, I’ll be free to reassign that post to another user just by changing its user ID.
Tom: The obvious solution to that problem is to add a Deny rule that says you can’t change the user ID field of a document. That’ll work, but of course, you might miss some other fields that shouldn’t be user-editable.
Sacha: Maybe a better solution is, instead of using a blacklist where you specify which fields you know you shouldn’t be able to edit, you can specify a whitelist.
Tom: But you’d want to be careful here as well. Because if you have one Allow callback that checks the user ID, and a second Allow callback that checks the whitelisted fields, then you’ll run into another common problem which is forgetting that you only need one of the Allow callbacks to match. Which is probably a reason to think about generally sticking on the side of Deny callbacks, by the way.
Another thing to watch out for is the fact that there’s more than one Mongo modifier. Often, we only use set thinking about Mongo as we might think about SQL. But there are other operators.
If you only check the set operator in your Deny callbacks, you could very easily miss, for instance, a push or an inc operator that may end up having the same effect that you were trying to stop. Again, you could have a whitelist for operators, would be another approach.
Sacha: I think Allow & Deny, it’s a cool feature, and it’s very helpful in the beginning. But, like we just saw, there are a lot of potential security issues with it. I think we both agree that methods are probably a bit more reliable overall.
Tom: Definitely. It’s hard to get Allow & Denys right, and what you tend to end up doing once you’ve been burned a few times is get really paranoid with your Allow & Denys, and make them really restrictive and use really heavy blacklists. You just say, “You’re only allowed to set this one field.” If you’re going to do that, you probably should just write a method anyway.
Sacha: Let’s talk about methods. What a method is is basically a bit of code that will run on the server, but can be called from the client.
Tom: In many ways, methods and the considerations needed to take into account are very similar to the publications. Because, in both cases, you have a user ID that you can look at. In both cases, you need to worry about things like rate-limiting. You need to worry about things like making sure you only return the right set of documents or write to the right set of documents.
It’s probably more like a simple RESTful API-type of things that you need to worry about in any webstack.
Sacha: One thing that you do need to keep in mind, though, is that just because it’s running on the server, doesn’t mean it’s automatically secure. You still need to check the user ID, you still need to check the types of the arguments.
Also, methods do not follow Allow/Deny rules, because those are only for the client. If you define all your security layer in Allow/Deny, then you add a method, that method will not have any security out of the box.
Tom: So methods are closer to the metal.
Sacha: It’s fair to say they’re more explicit. So that’s what makes them both a little bit more work to write sometimes, but also a lot easier to get right. There’s a lot less magic happening.
Tom: It’s fair to say that magic and security don’t mix too well.
Other Security Concerns
Sacha: You’re right. We’ve had an overview of the two big areas of security in Meteor, read and write. But there are a few other things I wanted to mention as well. For example, just as with other frameworks, you have to watch out for XSS or cross-site scripting attacks which is when malicious users enter content in your site, and when that content gets outputted and parsed as HTML, it can do bad things.
Another thing to be aware of is that the user profile object is public and writeable by default.
Tom: This is a convenience. It’s also published by default, it’s intended to be the user’s properties that they have access to and they can write.
Sacha: For example, if you have an admin flag, don’t store it on user.profile. Because now, suddenly, anybody can set their own admin flag to true and become an admin.
Sacha: To close things out, I just wanted to mention a few resources that are useful if you’re interested in Meteor security. First of all, Josh Owens has a few good posts on security on his blog. He also has a security checklist if you sign up for his email newsletter.
There are the packages we mentioned, MeteorHacks, Sikka and meteor-easy-security. Another good one is Meteor Security by Ongo Works. That’s a different API for Allow & Deny, so it lets you specify things in a way that’s a little more explicit and a little more secure, I think.
Tom: We have a couple of posts on our blog on the subject, and we’re always adding to that. So check there as well. Might discover a few more gems.
Sacha: Finally, Pete Corey’s site one pixel solid tomato, spelled 1-P-X, is also a really good resource for security. Especially that small gotchas that you might miss.
Tom: In conclusion, we’ve focused in this podcast on a lot of the things that can go wrong with Meteor and security. I think that makes sense, but what to keep in mind is that Meteor also is great. It does some stuff for you that would be very difficult to get right, security-wise, yourself.
For instance, the passwords package takes care of a lot of encrypting the password, sending it over, storing it in the database in encrypted form in a way that means just don’t have to worry about it. You’ll just get it right in your app.
Likewise, the other accounts packages such as the [inaudible 19:14] packages do the same. You can trust that they’ve been audited really thoroughly by people who know what they’re doing, which maybe might not be the case if you wrote it yourself.
Sacha: Wrapping up, Meteor security issues are not worse or better than Rails, PHP, or node. They’re just sometimes a little but different. Like with everything, you need to know what you’re doing and not assume that everything will just be secure out of the box. Because a lot of the time, you do need to put in the time and make sure you’re not making any mistakes.