NoSQL injection is what happens when an application builds a NoSQL query from user input without checking the shape of that input. Instead of breaking out of a string the way classic SQL injection does, the attacker slips in a query operator or a whole object where the code expected a plain value. The database does exactly what the rewritten query asks, which is often not what the developer meant.
How NoSQL injection differs from SQL injection
SQL injection is a syntax attack. The attacker types a quote and a fragment of SQL, the input gets glued into a text query, and the parser reads the attacker’s fragment as code. The classic example is ' OR '1'='1.
NoSQL databases like MongoDB do not parse a text query in the same way. A query is a structured object, often JSON. So the attacker does not need to escape a string. They change the type of the input from a string to an object, and they put a query operator inside that object. The database treats the operator as a real part of the query.
Take an invented note taking app called Acme Notes. Its login route looks up a user by username and password:
// What the developer expects: two strings
db.users.findOne({ user: req.body.user, pass: req.body.pass })
// The intended query for a normal login
{ user: "alice", pass: "hunter2" }
The developer assumed req.body.user and req.body.pass are always strings. The JSON body of a request does not promise that.
The auth bypass with query operators
MongoDB has comparison operators like $ne (not equal), $gt (greater than), and $gte (greater than or equal). If the attacker can put one of these into the query, they can make the password check meaningless.
Instead of sending a password string, the attacker sends an object as the password value:
POST /login
Content-Type: application/json
{ "user": "admin", "pass": { "$ne": null } }
Now the query the app builds is:
db.users.findOne({ user: "admin", pass: { $ne: null } })
This reads as: find the admin user whose password is not equal to null. The admin password is some real string, which is not null, so the condition is true and the document comes back. The attacker is logged in as admin without knowing the password.
A variant drops the username too, so the query matches the first user in the collection:
{ "user": { "$gt": "" }, "pass": { "$gt": "" } }
Here $gt: "" means greater than the empty string, which is true for almost any stored value. Both conditions pass and the app returns a user.
The attacker never broke the query syntax. They changed a value into an operator, and the database followed orders.
Operator injection through query strings
This is not only a JSON problem. Many web frameworks parse bracket notation in query strings and form bodies into nested objects. Express with the qs parser is a common example. A request like this:
GET /search?user[$ne]=null
// gets parsed into
req.query.user === { "$ne": null }
If that value flows straight into a query, the attacker has injected an operator without sending any JSON at all. The same trick works in URL encoded form posts. So a route that looks like it only handles strings can still receive an object.
Operator injection versus JavaScript injection
There are two different shapes of NoSQL injection, and they need different fixes.
Operator injection
This is everything above. The attacker injects query operators such as $ne, $gt, $in, or $regex. The damage is bounded by what the query language can express, which is still enough for auth bypass, data extraction, and enumeration. A $regex value, for example, lets an attacker probe a secret one character at a time by watching which patterns return a match.
JavaScript injection with $where
MongoDB also lets some queries run server side JavaScript through the $where operator or the older mapReduce and eval features. If user input reaches a $where string, the attacker is no longer limited to query operators. They can inject JavaScript that runs inside the database:
// Dangerous: user input concatenated into a $where string
db.notes.find({ $where: "this.owner == '" + req.query.owner + "'" })
// Attacker sends owner = x' || '1'=='1
// The clause becomes always true, and worse expressions are possible
This is closer to code execution than to query manipulation. It is rarer because $where is used less often, but the blast radius is larger. Treat any use of $where with user input as a serious problem on its own.
How to detect NoSQL injection
The core test is simple: send an object or an operator where the app expects a string, then watch the response.
- Send a type change. In a JSON body, replace a string value like
"pass": "x"with"pass": {"$ne": null}. In a query string, tryfield[$ne]=nullorfield[$gt]=. - Watch the result count. A search that returned three rows for a real term but suddenly returns the whole collection for
{"$ne": null}is a strong signal that the operator reached the query. - Watch for auth bypass. If a login that should fail instead succeeds when you send an operator as the password, the query is being built from raw input.
- Probe with regex timing or matches. A
$regexvalue that changes which records come back, or that changes response time, tells you the value is being interpreted as an operator.
Run these checks only against an app you own or have permission to test. If you want the wider family of input bugs, our injection and input category covers the rest.
How to prevent NoSQL injection
- Validate and cast types. A field that should be a string must be a string before it reaches the query. Cast it, or reject the request if it arrives as an object. If
passis ever an object, the login should fail closed, not run the query. This single rule stops the operator bypass. - Use an allowlist of operators. If your app legitimately accepts some operators for filtering, list the exact ones you allow and drop every key that starts with
$otherwise. Do not try to blocklist the dangerous ones, since the list keeps growing. - Never pass user input into $where or server side JavaScript. Avoid
$where,mapReducewith user strings, and anyevalstyle feature. Rewrite the logic as a normal structured query. - Use the driver’s typed query builders. Build queries with explicit field comparisons in code rather than spreading a user supplied object into the filter. A schema layer that enforces types, such as a model definition, gives you the cast and the rejection for free.
- Sanitize at the edge. Strip or reject keys containing
$and.from request bodies and query objects before they reach the database layer.
The pattern is the same as the lesson from SQL injection: never let untrusted input decide the structure of a query. With NoSQL the structure lives in object keys and types, so that is where the check belongs. For short definitions of the terms used here, see the web security glossary.
Why NoSQL injection rewards understanding the app
You do not find NoSQL injection by replaying one fixed payload. You find it by understanding which fields the app expects as strings, where the framework quietly turns input into objects, and whether the query trusts the shape of that input. The bug is an assumption, that a value would always arrive as a string, and the way to find it is to test that assumption directly.
That is the kind of bug an autonomous researcher built to test an app’s assumptions is made to surface. You can read more about that approach on our about page.
Frequently asked questions
What is NoSQL injection?
NoSQL injection is an attack where an application builds a NoSQL query from user input without checking its type. Instead of escaping a string like classic SQL injection, the attacker sends a query operator or an object where a plain value was expected. In MongoDB a password value of {"$ne": null} turns the password check into “not equal to null”, which is true for any real password, so the database returns the user and the attacker is logged in.
How is NoSQL injection different from SQL injection?
SQL injection is a syntax attack: the attacker escapes a string with a quote and injects SQL that the parser reads as code. NoSQL databases work with structured queries, often JSON objects, so there is no string to escape. The attacker instead changes the type of the input from a string to an object and puts a query operator inside it. The database treats that operator as a legitimate part of the query.
What is the difference between operator injection and $where injection in MongoDB?
Operator injection inserts query operators such as $ne, $gt, or $regex into a query, which is enough for auth bypass and data extraction but stays within the query language. The $where operator runs server side JavaScript, so if user input reaches it the attacker can execute JavaScript inside the database. That is closer to code execution and has a larger blast radius, so user input should never reach $where.
How do you prevent NoSQL injection?
Validate and cast types so a field expected to be a string can never arrive as an object, and fail closed if it does. Use an allowlist of permitted operators and drop any key starting with $ otherwise. Never pass user input into $where or other server side JavaScript features, and build queries with the driver’s typed query builders or a schema layer rather than spreading a user supplied object into the filter.
