Meteor Blind NoSQL Injection


I recently came across a Meteor application, which had a publicly callable method 'users.count' that would return the count of users registered in the app. While this may not be significant from a threat assessment perspective, I decided to give it another look and dig a bit deeper.

By calling the users.count method with the argument {}, the backend would return 1923, the number of users registered.

However, by changing the argument to {"username": "kertojasoo"}, the result was 1. I was astonished - it worked.

Can you see the vulnerability here already?

In this post, I’ll cover how to exploit this kind of vulnerability, how to automate the process, how to find similar vulnerabilities in other applications and how to mitigate.

Exploiting

The following functions can be used to exfiltrate information from the database.

Example:

Meteor.call("users.count", {
    "username": "kertojasoo",
    "token": {$regex: '^[a-z].*'} // start's with [a-z] (26 possibilities)
}, console.log);

In this example, there is a token property attached to each user. If the return value for this query is 1, then we can deduce that the token for this user starts with a lowercase character. Now, we just have to reduce the character range by half and hope for the best:

Meteor.call("users.count", {
    "username": "kertojasoo",
    "role": {$regex: '^[a-m].*'} // start's with [a-m] (13 possibilities)
}, console.log);

Once you have figured out the first character, just prepend it to the regular expression and keep enumerating.

While there is no direct way to read the token of another user, this kind of boolean-based testing can be used to do an iterative binary search to find the correct value, and this is the reason I’d call this exploit “regex-based blind NoSQL injection”.

A naive (linear search) implementation of this exploit is available on GitHub.

Naive search GIF

If you are able to sign in to the application, executing Meteor.user() in the console can help you find user parameters to enumerate via this method. From the Meteor guide, you might also find it useful to extract "services.password.bcrypt" for each user, although cracking bcrypt hashes is a whole another topic.

Some method to the madness

Let’s take a step back here. What are Meteor methods, anyway?

Methods are Meteor’s way to have client-server communication - a way to call server code from the client. A ‘method’ itself is a JavaScript function that is assigned a name.

Here’s how a method could be defined on the server:

Meteor.methods({
  'users.count'({ filter }) {
    return Meteor.users.find(filter).count();
  }
});

And a very basic client would use it like that:

Meteor.call('users.count', (err, res) => {
  console.log(res);
});

Notice that this example does not validate user input. Having server code that is callable from the client also means that all methods need to implement authorization, otherwise malicious clients can abuse the server. It’s similar to how you have to secure all your API endpoints when dealing with REST.

Finding exposed Meteor methods

When testing a Meteor app, one of the first things to do is to enumerate all publicly callable methods. Some guides recommend opening up your browser’s developer tools searching the bundled JavaScript code for Meteor.call - but there exists a more efficient way to see all public Meteor methods.

Note: most of the following steps can be avoided if the Meteor app is running in development mode, in which case all of the source maps are available in DevTools.

Source maps Thank you for running your app in development mode!

I have also written a bash script that can help with automatic extraction of public methods, available on GitHub. Be sure to only run this against servers you are allowed to test!

Sample output Sample output of automatic Meteor methods extraction script

Step 1: Extract

When a Meteor application is built for deployment with meteor build, all JavaScript files and templates are packaged and minimized into a single file. This can also be emulated with meteor run --production. We can see the minimized code when we look at the source of a built Meteor application.

Looking into the JavaScript file, the last minimized line will contain all of the application-specific code. This is the line we want to dig into.

Step 2: Beautify

Beautifying the whole JavaScript bundle might not be feasible, but once you have excluded all the packages and boilerplate code and kept only the application-specific code, formatting should be a breeze. Most code editors can beautify JavaScript code, but there are also online tools for that if you’re in a hurry. It’s a lot easier to get an overview of what the minimized code is doing once it’s formatted properly.

Step 3: Filter

Searching the beautified source for .call(" and .methods( will yield you all the method names you can use. The source code can also provide hints about what arguments to test for.

Step 4: Experiment with findings

In the DevTools console, you can now try to call the found methods and see how they behave. The most simple primitive you can use is the following:

Meteor.call("method.name", console.log)

If you need to pass arguments, the following is useful:

Meteor.call("method.name", {key: "value"}, console.log)

Impact

At the time of writing this blog post Shodan.io reports 38,105 live servers hosting a Meteor application and BuiltWith shows information about 17,334 live websites using Meteor.

Exporting Shodan data resulted in 18666 sites that had the minified JavaScript bundle available at the standard location. Out of these, 9379 sites had any meaningful Meteor methods defined.

There are at least 659746 publicly exposed Meteor methods available on the internet. Average Meteor application exposes 70 methods.

Searching exposed method names for some juicy keywords results in the following:

---------------------------
│ Meteor Exposed Methods  │
---------------------------
│ Keyword  │ Count │  \b  │
---------------------------
│ update   │ 45430 │ 3724 │
│ get      │ 87102 │ 3701 │
│ insert   │ 14391 │ 2542 │
│ create   │ 12475 │ 1707 │
│ delete   │ 35230 │ 1813 │
│ add      │ 46434 │ 1078 │
│ count    │ 14674 │  401 │
│ fetch    │  1434 │  365 │
│ set      │ 61780 │  353 │
│ send     │ 21199 │  258 │
│ account  │ 11634 │  251 │
│ find     │  4299 │  223 │
│ password │  7870 │  176 │
│ upload   │  9363 │  175 │
│ read     │ 11303 │  171 │
│ submit   │   590 │   81 │
│ filter   │   555 │   77 │
│ token    │ 21557 │   44 │
│ open     │  3024 │   39 │
---------------------------

 Count:
   grep -riP "$word" | wc -l
 \b adds word breaks:
   grep -riP "\b$word\b" | wc -l

Mitigating

When using a development framework that promises to “ship more with less code”, do not take it as a sign to also skip over implementing input validation.

Meteor’s official guide recommends that all Meteor apps should use Methods to accept data input from the client, and the arguments accepted by each Method are restricted as tightly as possible. You should validate all passed arguments before they are used in any business logic.

Conclusion

In whatever way you prefer your client-side code to communicate with your backend, be it a RESTful API, websockets or Meteor methods, you have to make sure that all user input is validated and no unexpected behaviour can occur with malformed or even malicious data.

Reminder: stay safe and legal, do not test servers that you do not have the permission for.

Kert Ojasoo