What is NoSQL Injection?
NoSQL injection targets document-oriented and key-value databases like MongoDB, CouchDB, and Redis. Unlike traditional SQL injection, attackers don’t inject SQL syntax — instead, they inject query operators or manipulate JSON structures to alter the intended query logic.
MongoDB’s query language accepts rich JSON objects with special operators (e.g., $gt, $ne, $where, $regex). If user-supplied JSON or form data is deserialized and passed directly into a query, an attacker can inject these operators to bypass filters, extract arbitrary documents, or enumerate the entire database.
How exploitation works
A Node.js login endpoint queries MongoDB with user-supplied credentials:
// Application query: { username: "alice", password: "secret" }
db.users.findOne({ username: req.body.username, password: req.body.password });
An attacker sends this JSON body:
{ "username": "admin", "password": { "$ne": "" } }
MongoDB evaluates { "password": { "$ne": "" } } — password not equal to empty string — which is true for every user. The attacker authenticates as admin without knowing the password.
Vulnerable code examples
Node.js / Express + MongoDB
// VULNERABLE: Body parsed as JSON and passed directly to MongoDB query
app.post('/login', async (req, res) => {
const user = await db.collection('users').findOne({
username: req.body.username, // Could be an object like { $ne: null }
password: req.body.password
});
if (user) return res.json({ token: generateToken(user) });
return res.status(401).json({ error: 'Invalid credentials' });
});
PHP / MongoDB
// VULNERABLE: $_POST data directly used in MongoDB query
$user = $collection->findOne([
'username' => $_POST['username'],
'password' => $_POST['password'] // Attacker can pass ["$ne" => ""]
]);
Secure code examples
Node.js — type enforcement
// SECURE: Explicitly cast to string and validate format before querying
app.post('/login', async (req, res) => {
const username = String(req.body.username ?? '');
const password = String(req.body.password ?? '');
// Reject if not valid strings (operators would be objects)
if (typeof req.body.username !== 'string' || typeof req.body.password !== 'string') {
return res.status(400).json({ error: 'Invalid input' });
}
const user = await db.collection('users').findOne({ username, password: hashPassword(password) });
if (user) return res.json({ token: generateToken(user) });
return res.status(401).json({ error: 'Invalid credentials' });
});
Python / PyMongo — explicit string casting
# SECURE: Cast inputs to strings and hash passwords before querying
from pymongo import MongoClient
import hashlib
def login(username_input, password_input):
# Force string type — dict/operator injection impossible
username = str(username_input)
password_hash = hashlib.sha256(str(password_input).encode()).hexdigest()
user = db.users.find_one({"username": username, "password_hash": password_hash})
return user
What Offensive360 detects
- Unsanitized query objects — MongoDB
find(),findOne(),update()calls receiving user-controlled objects without type validation $whereoperator with user input — JavaScript expressions in$whereclauses that include tainted data (enables JavaScript injection)- Mongoose query selector injection — Mongoose models queried with unsanitized
req.bodyorreq.queryobjects - Array/object operator injection — Endpoints parsing JSON bodies that are passed directly into query selectors
Remediation guidance
-
Enforce type checking — Validate that fields expected to be strings are indeed strings before using them in queries. Reject objects or arrays.
-
Use schema validation — Libraries like Joi, Zod, or Mongoose schema validators enforce input shape before data reaches the database.
-
Hash passwords before comparison — Never compare passwords in plaintext within MongoDB queries; use bcrypt or Argon2 and compare hashes.
-
Avoid the
$whereoperator —$whereexecutes JavaScript on the server; never use it with user input, and disable it at the MongoDB level if not needed. -
Use an ODM/ORM appropriately — Mongoose’s typed models reduce injection risk, but raw
.find(req.body)patterns remain dangerous.