We all know that SQL injection (SQLi) is a thing. But it may surprise quite a few people that similar injection techniques are possible when the underlying database is a NoSQL database.

In this post, I’ll show a simple way of exploiting such a NoSQL injection in two ways.

Application

This attack can do at least the following:

  • Bypass authentication
  • Leak sensitive information

First, let’s look at authentication bypassing.

Hint: Code files can be found on my github.

Authentication bypass

Doesn’t this sound awesome? A simple trick to bypass authentication? Can it really be that simple? The short answer is: yes. Yes, it can be.

Let’s take a look at an authentication mechanism that uses the famous MongoDB:

Screenshot of PHP code

After establishing a connection, this code tries to get the user from the database whose username and password match the POST parameters username and password. If such a user is found, “Login succeeded” will be displayed.

I know, this is not in any way sophisticated, but it helps bringing my point across.

Here’s the database:

Screenshot of the users collection

How to bypass

Bypassing this is incredibly easy. Let’s do a proof of concept.

The Jupyter notebook I’m using here can be found on my github.

First, here’s a failing login attempt:

Failed login attempt in Jupyter notebook (check my github)

This, as expected, prints “Login failed” to the console. We will now bypass that login mechanism:

Successfully bypassing authentication in Python

Yes, it is that simple!

We simply alter the parameters username and password to include expressions. In this case we’re using $ne, which means “does not equal”. So we tell MongoDB to find all users that are NOT called steps0x29a and DON’T have steps0x29a set as their password, essentially bypassing the login.

This can be used to bypass website login forms with burpsuite, for example. Simply tell burpsuite to alter all POST requests (or whatever is appropriate) to include [$ne] in them and there you go.

Information disclosure

Bypassing a login form can be powerful in itself, of course. But as users often reuse their credentials, for example for SSH access, we might want to extract some real information from the database. The good news is: we totally can!

There’s another modifier like [$ne] (actually, there are several, see MongoDB’s documentation for details): $regex.

It works like [$ne], but matches data against a regular expression which we can control in our scenario. Yes, you read that right – MongoDB allows us to use regular expressions to leak information from it!

As this is a bruteforce approach, we first need to build a list of all characters that we want to try. Here’s how I did it, but you can (and should) build your own:

Building the alphabet

Python’s string.printable provides all characters that Python deems “printable”. As we’re dealing with regular expressions, we need to escape some of them in order to make it work. That’s why I’m importing re here, so we can use re.escape later on.

Now on to bruteforcing a username:

Bruteforcing a username

Let’s go through this step by step:

In bruteforce_user we start with a blank username (of course it’s possible to use a seed, e.g. “a” or “r” to nudge the algorithm in the right direction) and iterate over the alphabet one character at a time, escaping it with re.escape.

We build a payload by appending the next (escaped) character we want to test to the part of the username we know by now (starting with an empty one).

We then send the payload to the server and check the response.

If the (bypassed) login is successful, we check whether the username actually exists (the bypass will succeed with “adm”, but the check function will not; it will, however, succeed with “admin”).

The test_username function uses a slightly modified regular expression to verify usernames. We need it in order to recognize when we’ve identified a valid username.

That’s it. Passwords can be bruteforced the same way by simply swapping the parameters in the request and passing in the username as well:

Bruteforcing a user’s password

That’s where I’ll leave this topic for now. Of course there’s a lot that can be improved and added to this code and it is far from fool proof for sure.

Check out the Jupyter notebook on my github to play with the code and have fun! Don’t do stupid stuff 😉

Title image credits: cottonbro via Pexels