The Danger of Uncaught Exceptions

Below you'll find a sample chapter from Secure Meteor; a guide to help you learn the ins and outs of securing your Meteor application from a Meteor security professional. If you like what you read and you're interested in securing your Meteor application, be sure to read the entire book!

Uncaught exceptions in your Meteor methods, publications, and collection validators are largely nothing to worry about. The exception will be caught by the framework, logged, massaged into a user-friendly message, and returned to the client to inform them that something went wrong. Unfortunately, this is not the case for most HTTP endpoint solutions.

Imagine that we’ve integrated our shopping application with a third-party payment processing system. That third-party system uses web hooks to notify us as soon as possible whenever our customers’ payments are processed.

Our web hook looks something like this:

As you can see, we’re using meteorhacks:picker to construct our HTTP endpoints.

When our third party payment processor hits this endpoint, they’ll pass along a secret “payment token” that we can use to find the specific payment in our system that needs to be marked as processed.

We’re making two assumptions with this endpoint. First, we’re assuming that only our third party payment process will ever be making requests to the /api/process_payment endpoint. Second, we’re assuming they’ll always provide a valid token that maps to a payment in our system. Both of these assumptions lead to trouble.

Our second assumption is especially dangerous. Imagine if our endpoint received a bogus token that didn’t map to any existing payment. Our call to Payments.findOne will return undefined, and the subsequent call to payment.markAsProcessed() will cause a runtime exception. To make matters worse, this exception isn’t trapped by a try/catch block by our code, or by the underlying Picker library. If we attempt to call markAsProcessed on our undefined payment, an exception will be thrown that, depending on your version of Node.js, will crash your entire application.

Armed with this information, an attacker could easily craft a script that repeatedly calls out to this endpoint with bogus token values with the sole intention of repeatedly crashing your application and making it unavailable to other users:

Uncaught exceptions that happen in our Meteor methods and publications are always gracefully captured by the Meteor framework. It’s important to realize that when you venture out beyond Meteor’s territory, the world at large isn’t always so forgiving. Never let an uncaught exception bubble up to the Node.js event loop. This can result in a crash of your application, and if it’s a repeatable exploit, it paves the way for a dangerous Denial of Service vulnerability.

To prevent this vulnerability, we should have been more defensive with our error handling and wrapped our code in a try/catch block:

Now our invalid token value will still result in payment being undefined, but the exception thrown by trying to access markAsProcessed on payment will be caught by our catch block. Rather than crashing our entire server, a 500 error response will be sent back to the attacker.

When reaching for a solution to add HTTP endpoints to our application, there are many options to choose from. We can pick something like meteorhacks:picker, or nimble:restivus, something like simple:rest that offers a more opinionated solution, or we can even integrate directly with express. Each of these solutions offers different guarantees about exception handling, and it’s important to fully understand those guarantees, rather than making dangerous assumptions.