NoSQL Injection

Overview

NoSQL injection follows the same core idea as SQL injection: untrusted input changes the meaning of a backend query. In practice, MongoDB is the most common place to study it. These notes cover the MongoDB document model, query filters, operator injection, authentication bypass, regex-based password extraction, syntax injection, and practical defensive patterns.

MongoDB Basics

MongoDB stores data as documents rather than rows in relational tables. A document is essentially a key-value structure, similar to a dictionary or JSON object.

{
  "_id": "5f077332de2cdf808d26cd74",
  "username": "lphillips",
  "first_name": "Logan",
  "last_name": "Phillips",
  "age": "65",
  "email": "lphillips@example.com"
}

Documents with similar purpose are grouped into collections, and collections are grouped into databases.

How MongoDB Queries Look

Instead of SQL strings, MongoDB commonly uses structured filter objects.

Match by Field Value

['last_name' => 'Sandler']

Match Multiple Fields

['gender' => 'male', 'last_name' => 'Phillips']

Use an Operator

['age' => ['$lt' => '50']]

The nested operator style is exactly why NoSQL operator injection matters. If user input is allowed to become an object instead of a simple scalar value, the attacker may be able to rewrite query logic.

Two Main NoSQL Injection Types

In real applications, operator injection is usually more common than syntax injection because many libraries make direct syntax breaking harder, but still allow attacker-controlled arrays or objects to reach the query builder.

Operator Injection in Login Logic

Consider a PHP login flow that builds a MongoDB query directly from POST values:

$user = $_POST['user'];
$pass = $_POST['pass'];

$q = new MongoDB\Driver\Query([
    'username' => $user,
    'password' => $pass
]);

If the application expects both values to be strings, it looks harmless. But if the attacker can send arrays instead of plain strings, those arrays may be interpreted as MongoDB operators.

Authentication Bypass with $ne

If the attacker can turn the username and password inputs into operator objects such as:

$user = ['$ne' => 'xxxx'];
$pass = ['$ne' => 'yyyy'];

then the resulting filter becomes:

[
  'username' => ['$ne' => 'xxxx'],
  'password' => ['$ne' => 'yyyy']
]

That asks the database for any record where the username is not xxxx and the password is not yyyy. If any user matches, authentication succeeds.

In PHP-style parameter notation, the attacker can submit this as:

user[$ne]=xxxx&pass[$ne]=yyyy

Key lesson: if the application allows nested structures in input and passes them directly into the query object, the attacker may be able to replace a value comparison with an operator comparison.

Choosing Which User You Become with $nin

The previous bypass usually logs you in as the first returned document. If you want to exclude specific users and force the application toward another account, $nin becomes useful.

[
  'username' => ['$nin' => ['admin']],
  'password' => ['$ne' => 'aweasdf']
]

You can keep expanding the exclusion list until the returned record corresponds to the account you want.

Password Extraction with $regex

NoSQL injection can also be used to extract passwords character by character. A common approach is to use regex-based conditions and infer success from whether login succeeds or fails.

First, determine the password length using a pattern such as:

^.{5}$

Then test candidate prefixes:

^a....$

If the application accepts the login condition, you have learned that the password starts with a. Repeating the process recovers the full value, much like blind extraction in SQL injection.

Syntax Injection

Syntax injection is less common but more direct. It occurs when attacker input is concatenated into a JavaScript-based MongoDB query string rather than being passed as a safe value to built-in query helpers.

Example vulnerable pattern:

for x in mycol.find({"$where": "this.username == '" + username + "'"}):

If a single quote causes an error or a crafted boolean condition changes the result, you likely have syntax injection rather than operator injection.

Confirming Syntax Injection

You can often validate the issue using true or false conditions inside the injected JavaScript logic.

admin' && 0 && 'x
admin' && 1 && 'x

If the application behaves differently between the two, the input is influencing executable query logic.

Dumping Data Through Syntax Injection

Once syntax injection is confirmed, a payload that always evaluates true can broaden the result set. In the supplied example, a payload such as:

admin'||1||'

turns the query into a condition that matches every document, disclosing all email addresses returned by the find operation.

Why Syntax Injection Is Rarer

Operator injection often appears because developers pass structured input directly into safe-looking query builders. Syntax injection is rarer because it usually requires the developer to build custom JavaScript query strings manually, for example with $where concatenation.

That means operator injection is usually the first thing to test, but syntax injection is still worth checking if you see custom JavaScript query logic or verbose backend errors.

Testing Approach

Defensive Guidance

Key Takeaways