CollectionFS Safety Considerations

Written by Pete Corey on Apr 4, 2016.

The ability to upload files is a core piece of functionality in many web applications. Because of this, many libraries have sprung up around the topic of managing and facilitating file uploads. Arguably the most popular Meteor file upload package is CollectionFS.

CollectionFS is ubiquitous throughout the Meteor community due to its extensive functionality and its ease of use. In fact, it’s so easy to use that many developers simply drop it into their application and move on to their next feature without considering the implications that file uploading might have.

From a security and performance perspective, there are several things you should consider and make conscious choices about before adding file uploading to your application:

  • File size limiting
  • File count limiting
  • File type restrictions
  • File handling and processing

Let’s dive into each of these topics and explore why they’re so important.

File Size Limiting

The size of files being uploaded into your system quickly becomes important when you begin working with those files. Are you processing the files that are uploaded in any way? In doing that processing, are you attempting to load the entire file’s contents into memory?

Loading a massive file into memory can quickly lead to performance issues and server crashes. Node.js applications have a default maximum of 1.76GB of available memory. If a user were to upload a file that’s around 1.76GB or larger, it would lead to the server crashing and the application being completely unavailable during the restart.

Thankfully, restricting an upload’s file size is a very simple process when using CollectionFS. The following code creates a Files collection and uses the filter object to specify a maximum file size of 100MB (1e+8 bytes).

Files = new FS.Collection("files", {
  stores: [ ... ],
  filter: {
    maxSize: 1e+8
  }
});

Any files larger that 100MB will be rejected by this filter.

File Count Limiting

Along with limiting the size of files being uploaded, you should also limit the number of files uploadable by users of your system.

Imagine that you’re using CollectionFS to upload files into an S3 bucket. Left unchecked, a malicious user might upload hundreds or thousands of very large files into this bucket, drastically increasing the amount of storage space being used.

Without some kind of alerting, you may not notice this until your next AWS billing cycle where you’ll find a notably increased S3 bill!

Adding a maximum limit to the number of files in your CollectionFS stores is accomplished by added a custom insert rule to your file collection:

Files.allow({
  insert: function() {
    return Files.find().count() <= 100;
  }
});

In this example, file uploads will be rejected if there are already 100 files uploaded to your stores.

We could easily tweak this example to allow a maximum number of files per user or per any arbitrary group of users.

File Type Restrictions

When files go up, they usually come down. Any system that allows for the uploading of files usually intends for those files to be downloaded and used at some point in the future.

However, what if a user could upload any kind of file they wanted? Imagine the repercussions of a user uploading malicious scripts, executables or viruses to your applications. Those files might be downloaded and run by some other user, leading to a compromise of their system.

Most applications only work with a small set of file types. It’s good security practice to only allow files of those types to be uploaded. The best way to restrict the file types allowed into your system is to allow a set of expected file extensions:

Files = new FS.Collection("files", {
  stores: [ ... ],
  filter: {
    allow: {
      extensions: ["pdf", "doc", "docx"]
    }
  }
});

This example only allows PDFs and Word documents to be uploaded.

Filtering on file extension is considered safer than filtering on content type. The content type of an uploaded is provided by the client and can easily be spoofed.

Additionally, always whitelist expected file extensions, rather than blacklisting disallowed extensions. Blacklisting creates the possibility that a harmful file extension was forgotten, and could still be uploaded.

File Handling and Processing

When working with uploaded files, always act defensively. Never assume that the file is well-formed or that it will conform to your assumptions. Bugs in file processing algorithms have historically led to some severe vulnerabilities.

CollectionFS’s transformWrite runs in an unprotected thread of execute. This means that any uncaught exceptions that bubble up out of this method will escalate all the way to the event loop and crash the application. Once the server restarts, CollectionFS will notice that the transformation was not success and will re-attempt to transform the file, crashing the server in the process.

This kind of repeated crashing can leave your application completely inaccessible to users until the file having problems is removed from your CollectionFS store. A malicious user may intentionally create a crash loop to deny service to your application.

Final Thoughts

Working with files can be a dangerous proposition. Thankfully, CollectionFS and its associated storage drivers takes some of the danger out of our hands. In most circumstances we don’t have to worry about things like directory traversal vulnerabilities, or the possibilities of arbitrary code execution.

As we’ve seen, there are still things we need to be considerate of. If you follow these suggestions and spent time thoroughly analyzing your file upload system, you should have nothing to worry about.

Bypassing Package-Based Basic Auth

Written by Pete Corey on Mar 28, 2016.

In a previous post, I talked about using Basic Authentication to hide your Meteor application from prying eyes. Unfortunately, the most straight-forward way of implementing this kind of protection has its flaws.

To see those flaws, let’s imagine that we’ve set up a basic Meteor application with the kit:basic-auth package and a Meteor method:

Meteor.methods({
  foo: function() {
    return "bar";
  }
});

When we try to navigate to the application (http://localhost:3000/), we notice that we can’t access the application without valid credentials. Great!

Bypassing Basic Auth

However, Jesse Rosenberger recently pointed out that kit:basic-auth, and similar packages such as jabbslad:basic-auth, do not provide Basic Auth protection for WebSocket connections. This means that any external user can easily bypass this authentication mechanism and access your Meteor methods and publications.

For example, an external user could connect directly to your application using Meteor’s DDP API and call your "foo" method:

var connection = DDP.connect("http://localhost:3000/");
connection.call("foo", function(err, res) {
  console.log(res);
});

Any unauthorized user that runs the above code will receive a result of "bar" from the "foo" method.

This is a bad thing.

Calling In the Dark

While the DDP API gives users access to all of your Meteor methods and publications, it doesn’t reveal those methods and publications. In order to call a method or subscribe to a publication, a user needs to know its name.

However, this kind of security through obscurity shouldn’t be considered any real protection. An attacker eager to discover your DDP endpoints could build a brute forcer that guesses method and publication names in an attempt to uncover your endpoints.

A Better Basic Auth

At first glance, the kit:basic-auth and jabbslad:basic-auth packages seem to be doing all the right things. They’re injecting the Basic Auth check as a piece of connect middleware at the head of the stack which, in theory, should catch all HTTP traffic and verify the user’s credentials.

Unfortunately, the Meteor framework establishes the socket connection long before any of these middleware methods are called. This means that Basic Auth is ignored during the WebSocket handshake and upgrade process.

One possible technique for overcoming this middleware issue is to “overshadow” all "request" and "upgrade" listeners and inject our Basic Auth check there. The Meteor framework does this exact thing to support raw WebSocket connections.

However, a more straightforward approach to this problem may be to move your application behind a proxy such as HAProxy, or NGINX and implement Basic Auth at that level. The proxy would protect all assets and endpoints, including the /sockjs/.../websocket endpoint, which is used to establish a WebSocket connection with the server.

Final Thoughts & Thanks

I’d like to give a massive thanks to Jesse Rosenberger who pointed out this issue to me, and gave me a huge amount of very helpful information and observations.

I’d also like to apologize to anyone hiding applications behind a package-based Basic Auth guard based on my advice. I’ve updated my previous post on this subject to reflect what I’ve learned and pointed out the current shortcomings of this package-based approach.

NoSQL Injection in Modern Web Applications

Written by Pete Corey on Mar 21, 2016.

Last month I was lucky enough to be able to attend and speak at the first ever Crater Remote Conference!

I gave a talk entitled “NoSQL Injection in Modern Web Applications”. The talk was heavily focused on exploiting NoSQL injection vulnerabilities in applications using MongoDB. The bulk of the talk was spent in a hands-on demo showing how a malicious user could approach and attack a Meteor application vulnerable to these types of attacks.

Check out a recording of the presentation below, and be sure to watch a few of these highlights!

02:41 - Why security?
04:57 - What is “NoSQL Injection”?
12:25 - Grabbing all products by exploiting a publication.
17:36 - Getting all carts by exploiting a publication.
20:20 - Getting all carts through a .findOne query.
23:42 - Removing all user carts in the system.
25:26 - Modifying product prices.
29:40 - Escalating myself to admin level permissions.
34:55 - MongoDB denial of service through a .find query.
38:55 - How do we fix it?
42:30 - Why pick on MongoDB?
44:10 - Are other NoSQL databases safe?
47:40 - Q&A with Josh Owens.

At the end of the talk, I linked to Rob Conery’s Meteor Shop. You may also be interested in his fantastic PluralSight course on building the application from the ground up.

I also linked to my own package, Check Checker (east5th:check-checker), which helps you find methods and publications within your Meteor application that aren’t being thoroughly checked.

I had a blast watching the Crater Conf talks this year, and I’m looking forward to the next conference!