I recently came across a Meteor application which had a publicly callable method named "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 the risks.

Exploiting

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

Example:

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:

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 another user’s token, 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.

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 official Meteor guide, you might also find it useful to extract "services.password.bcrypt" for each user, although cracking bcrypt hashes is a whole other 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:

And a very basic client would use it like that:

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 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.

Thank you for running your app in development mode!

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

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 (specifically, the line that starts with var require= ) 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 simplest 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

Shodan.io currently reports 38,105 live servers hosting a Meteor application. BuiltWith shows information about 17,334 live websites currently using Meteor.

Exporting Shodan data resulted in 18,666 sites that had the minified JavaScript bundle available at the standard location, and 9,379 sites had any meaningful Meteor methods defined.

Meteor applications scraped from Shodan.io

There are at least 659,746 publicly exposed Meteor methods available on the internet. The 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

While an exposed method does not directly translate to a vulnerable method, the attack surface is there — and it’s huge.

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 Meteor methods, WebSockets or a RESTful API — you have to make sure that all user input is validated and no unexpected behavior 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.