Intercepting All Queries in a Meteor Application

Written by Pete Corey on Mar 27, 2017.

It’s probably no secret to you that I’m working on a new project called Inject Detect. As I mentioned last week, as a part of that project I need a way to intercept all MongoDB queries made by a Meteor application.

To figure out how to do this, we’ll need to spend a little time diving into the internals of Meteor to discover how it manages its MongoDB collections and drivers.

From there, we can create a Meteor package that intercepts all queries made against all MongoDB collections in an application.

Hold on tight, things are about to get heavy.

Exploring MongoInternals

One way of accomplishing our query interception goal is to monkey patch all of the query functions we care about, like find, findOne, remove, udpate, and upsert.

To do that, we first need to find out where those functions are defined.

It turns out that the functions we’re looking for are defined on the prototype of the MongoConnection object which is declared in the mongo Meteor package:


MongoConnection.prototype.find = function (collectionName, selector, options) {
  ...
};

When we instantiate a new Mongo.Collection in our application, we’re actually invoking the MongoConnection constructor.

So we want to monkey patch functions on MongoConnection. Unfortunately, the MongoConnection object isn’t exported by the mongo package, so we can’t access it directly from our own code.

How do we get to it?

Thankfully, MongoConnection is eventually assigned to MongoInternals.Connection, and the MongoInternals object is globally exported by the mongo package.

This MongoInternals object will be our entry-point for hooking into MongoDB queries at a very low level.

Monkey Patching MongoInternals

Since we know where to look, let’s get to work monkey patching our query functions.

Assuming we have a package already set up, the first thing we’ll do is import MongoInternals from the mongo Meteor package:


import { MongoInternals } from "meteor/mongo";

Let’s apply our first patch to find. First, we’ll save the original reference to find in a variable called _find:


const _find = MongoInternals.Connection.prototype.find;

Now that we’ve saved off a reference to the original find function, let’s override find on the MongoInternals.Connection prototype:


MongoInternals.Connection.prototype.find = function(collection, selector) {
    console.log(`Querying "${collection}" with ${JSON.stringify(selector)}.`);
    return _find.apply(this, arguments);
};

A diagram of a monkey patch.

We’ve successfully monkey patched the find function to print the collection and selector being queried before passing off the function call to the original find function (_find).

Let’s try it out in the shell:


> import "/imports/queryMonkeyPatcher"; // This is our monkey patching module
> Foo = new Mongo.Collection("foos");
> Foo.find({bar: "123"}).fetch();
Querying "foos" with {"bar": "123"}.
[{_id: "...", bar: "123"}]

As long as we import our code before we instantiate our collections, all calls to find in those collections will be routed through our new find function!

Now that we know our find monkey patch works, we can go ahead and repeat the procedure for the rest of the query functions we’re interested in.

How is This Useful?

This kind of low-level query interception can be thought of as collection hooks on steroids.

Josh Owens uses this kind of low-level hooking to explain every query made by an application with his Mongo Explainer Meteor package.

Similarly, Inject Detect will make use of this query-hooking-foo by collecting information on all queries made by your application and sending it off to be inspected for potential NoSQL Injection attacks.

This kind of low level hooking is an incredibly powerful tool that can accomplish some amazing things if used correctly.

How am I Building Inject Detect?

Written by Pete Corey on Mar 20, 2017.

I recently announced that I’m working on a new project called Inject Detect. It’s an understatement to say that I’m incredibly excited about this project!

Since announcing the project, I’ve spent the last couple weeks carefully considering my best steps forward. I’ve finally landed on what I believe is a solid plan of attack for building out Inject Detect, and I want to share it with the world.

Let’s dig into it!

What’s the Main Focus?

The first step in any project is to build a thorough understanding of the problem we’re trying to solve and understand how solving that problem brings value to our client or customer.

In the case of Inject Detect, our goal is to detect and notify Meteor application owners of NoSQL Injection attacks as they happen.

We give our customers real-time, actionable information to help them protect their applications and safeguard them against attack. A valuable by-product of this constant vigilance is peace of mind.

What does this mean for us?

We need to approach our solution with this value in the forefront of our mind. Everything we do should first and foremost maximize value for our customers.

High Level Architecture

From a very high level, Inject Detect is composed of two major components: a Meteor package that is installed in each monitored application, and a server to aggregate data sent from those applications and alert customers of potential NoSQL Injection attacks.

The Meteor package’s primary job is to hook into all queries made against a Meteor’s application’s MongoDB database and send that query information up to the server.

The server sifts through these incoming queries, comparing them to a set of expected queries for a given application. If an unexpected query is detected, the server sends a notification to the application owner, alerting them of a potential problem.

Collecting Queries on the Client

The fundamental function of Inject Detect is to sleuth over all queries made against a MongoDB database.

There are multiple ways of accomplishing this:

There are tools that use the MongoDB Profiler to observe queries in real-time. Unfortunately, the level of profiling required to intercept all queries can negatively affect database performance.

There are tools like MongoReplay that can be installed alongside MongoDB and act as proxies, intercepting MongoDB queries as they come in off the network. Unfortunately, installing these kinds of tools isn’t possible on hosted MongoDB solutions like Compose or Atlas.

So what are we left with?

The last option is to intercept queries at the application level. In the context of a Meteor application, this means hooking directly into the Mongo.Collection functionality at a low level.

Since this is the only option that won’t negatively impact database-level performance and will work for any type of MongoDB installation, this is the option for us.

But What About Sensitive Query Data?

Application owners may be concerned about sending full query objects off to a third-party service. Thankfully, Inject Detect doesn’t need to whole query object!

Imagine an application has a query that is used to authenticate a user based on their “resume token”:


Meteor.users.find({
  "services.resume.loginTokens.hashToken": "ABC123..."
})

In the wrong hands, this query information can be used to impersonate the user with the "ABC123..." resume token. This isn’t the kind of information you want to trust to third parties.

Fear not, Inject Detect respects your privacy!

Inject Detect only needs the structure of the query to detect potential NoSQL Inject attacks, not the fully populated query object.

In this example, we’d be send the following information up to the Inject Detect server:


{
  collection: "users",
  query: {
    "services.resume.loginTokens.hashToken": String
  }
}

Rather than being sent the entire query object, complete with sensitive data, we’re sending a schema of the query.

We don’t care about the value of hashToken, we just care that it’s a String.

This is enough information to detect potential abuse, and keeps private customer data where it should be - safe and sound in your application.

Prioritizing Performance on the Client

But we have to consider more than just privacy…

It wouldn’t be the best idea to send a request to Inject Detect for every query made in a client application. The Inject Detect server would quickly be overwhelmed by a huge number of incoming requests, and the client application would be overburdened with outbound requests.

Talk about inefficient!

Instead, the client-side Meteor portion of Inject Detect batches queries as they’re intercepted and only sends them to the server every N seconds.

For the initial release of the application, I’m planning on sending query batches to the server at most once every thirty seconds.

This will alleviate any potential performance issues for both Inject Detect and applications using the Inject Detect Meteor package, and the cost of being as close to “hard real-time” as we can get.

The Inject Detect Server

The Inject Detect server will listen for incoming queries from your Meteor application and compare them against sets of known queries that are made by your application.

If an unexpected query is detected, Inject Detect will notify you immediately. Additionally, it will try to identify which query is being exploited to give you immediate insight into where to look in your application.

But what about the nuts and bolts?

For a variety of reasons, the Inject Detect server will be implemented as an Elixir application.

The “core domain” of the application will be implemented using Event Sourcing techniques. I’ve been using event-based systems extensively for client projects with great success.

The Inject Detect front-end application will initially be implemented as a Phoenix application for simplicity.

We’ll dig into these details in future posts.

Final Thoughts and Next Steps

I’m very excited to begin working on this project and even more excited to bring Inject Detect into the world.

NoSQL Injection seems to be one of the most widespread and under-acknowledged security issues in Meteor applications (and in many other applications using MongoDB).

Inject Detect will help application owners detect when these attacks happen, and help track down their root causes.

Next week, we’ll dive into the Meteor package side of Inject Detect and discuss how we can hook into all database queries made by a Meteor application at a low level.

In the meantime, focus on security!

Why Security?

Written by Pete Corey on Mar 13, 2017.

I’m a software developer, but I spend a lot of time thinking and worrying about software security. In fact, I’ve even recently started working on a software security service called Inject Detect!

Sometimes my friends and coworkers ask why I, as a software developer, spend so much time writing about security. Why don’t I give up on development and focus on security full-time?

Security is fundamental to everything we do as software creators. It is an underlying assumption that makes everything we do possible. We spend countless hours building an effective team, developing amazing software and nurturing trust with our users, but all of that falls to the floor without security.


Imagine your company is doing well. Your application is a pleasure to use, and your user base is rapidly growing. You’ve attracted investors and you’ve built yourself an amazing team.

But suddenly, everything changes. A malicious user has managed to find and exploit a severe vulnerability within your application. Their attack has negatively impacted hundreds users.

The hard earned trust between those affected users and your company vanishes instantly. Other users, when they learn of the attack, quickly begin to lose trust as well. Now, one of the first results when people google your product is a scathing TechCrunch article outlining the gory details of the attack. Soon, investors lose interest. With their lack of support and a rapidly dwindling user base, you realize that you won’t be able to make payroll this month.


The question of “why security?” is answered simply: Because everything we do depends on it.

Security isn’t something that can be tacked on at the end of the software development process. Building vulnerability-free software is a holistic process, and security should be considered along every step of the way.