NoSQL injectionMongoDB injectiondocument store injectionoperator injection$where injection
TL;DR
Attacker-controlled input embedded into NoSQL queries (MongoDB, Redis, Couchbase) that subverts query intent — bypassing auth, exfiltrating data, or executing server-side code.
Explanation
NoSQL injection is the document-store equivalent of SQL injection, but with attack vectors specific to each engine. In MongoDB, the most common patterns are: operator injection where attacker input becomes a query operator object (e.g. submitting `{"$ne": null}` as a password to bypass a `find({user, password})` query); JavaScript injection via the `$where` operator or `mapReduce`, which executes attacker-controlled JS server-side; and array/object payload injection in fields the application expects to be strings. Redis vulnerabilities cluster around `EVAL`, `SCRIPT LOAD`, and command injection through unvalidated arguments to Lua scripts. Couchbase and other engines have N1QL injection that closely mirrors SQL injection. The unifying defence is the same as SQL: never concatenate untrusted input into queries; use parameterised query builders, type-cast inputs to expected scalar types before query construction, and validate that JSON inputs match a strict schema. PHP's MongoDB driver (`mongodb/mongodb`) accepts BSON arrays — type-casting `$_POST` values to strings before passing them to a query is a critical defensive step.
How It's Exploited
Login bypass: POST `username=admin&password[$ne]=x` — MongoDB matches any password where the field is not equal to 'x', returning the admin record. Data exfiltration: `username[$regex]=^a` — enumerate accounts character-by-character. RCE on `$where`: `username=admin'; sleep(5000); var x='` — server-side JS execution.
Watch Out
⚠ json_decode($input, true) returns associative arrays — passing the result into a query is the exact same vulnerability as passing $_POST directly. Type-cast and validate every field individually.
Common Misconception
✗ NoSQL databases are immune to injection because they don't use SQL. They are not — they accept structured query objects, and any pathway that lets an attacker control the structure of those objects (not just the values) is an injection vector. Operator injection with `$ne`, `$gt`, or `$where` can bypass authentication or extract data without ever touching SQL syntax.
Why It Matters
PHP applications that pass `$_POST` values directly into MongoDB queries are routinely exploitable via JSON-encoded operator payloads. Authentication bypass is trivial — login forms that compare `find(['user' => $u, 'pass' => $p])` accept `pass[$ne]=` as a query-string payload that matches any password. Stored data exfiltration follows from there.
Common Mistakes
Passing `$_POST` arrays straight into MongoDB queries — PHP arrays serialize to BSON objects, turning user input into operator structure.
Using `$where` with concatenated user input — equivalent to executing attacker-controlled JavaScript server-side.
Assuming JSON parsing sanitises input — `json_decode` happily produces nested arrays that become query operators.
Treating Redis as injection-free — `EVAL` with concatenated arguments, or commands constructed from user input, are RCE-class vulnerabilities.
Filtering only string-keys without filtering values — operator injection happens through values that contain keys starting with `$`.
Avoid When
Code uses a typed query-builder with parameterised values and no raw arrays — validate that's actually the case before assuming safety.
When To Use
Reviewing any PHP code that builds MongoDB or Redis queries from request input.
Auditing authentication, search, and update endpoints in document-store applications.
Setting up CI rules to catch direct $_POST/$_GET use in query construction.
Code Examples
💡 Note
Casting to (string) is the minimal fix; production code should also validate input shape and use $eq explicitly when comparing user input to fields, never letting raw associative arrays reach the query.
✗ Vulnerable
// ❌ MongoDB query built from raw $_POST — accepts operator injection
$user = $collection->findOne([
'username' => $_POST['username'],
'password' => $_POST['password']
]);
// Attacker submits password[$ne]=x — matches any non-'x' password.
// Authentication bypass is one form post away.
✓ Fixed
// ✅ Cast to scalar string and validate — operator structure cannot survive
$username = (string) ($_POST['username'] ?? '');
$password = (string) ($_POST['password'] ?? '');
if ($username === '' || $password === '' || strlen($password) > 1024) {
throw new InvalidArgumentException('Invalid credentials format');
}
$user = $collection->findOne([
'username' => $username,
'password' => password_verify_lookup($password)
]);
// For arbitrary user input that must be JSON, validate against a schema
// (e.g. opis/json-schema) before passing fields to the query builder.
Type-cast every user-supplied value to its expected scalar type (string, int) before placing it in a query array. Reject inputs whose JSON-decoded form contains keys starting with `$`.