Server-side
NoSQL Injection: The Complete Practical Guide to MongoDB Injection
What NoSQL injection is, how operator and type confusion bypass auth and extract data in MongoDB, and how to prevent it. With free labs to practice on.
Published

NoSQL injection is SQL injection's cousin for document databases. The root cause is identical: untrusted input changes the meaning of a query. What changes is the grammar. A MongoDB query is not a string of SQL text, it is a structured object, so you do not break out of it with a quote. You abuse the fact that the driver accepts a rich query language made of operators, and you slip an operator in where the application expected a plain value. One operator in a login form and the password check stops checking anything. Want to feel it rather than read about it? Sign up free and try it on a live target.
What is NoSQL injection
The mental model in one sentence: the application meant to compare your input as a value, and you turned it into part of the query. In SQL injection you do that by escaping the quotes around a string. In MongoDB there are no quotes to escape, because the query is already a data structure. Instead you change the type of your input from a string to an object, and that object carries a query operator the database obeys.
That difference is the whole topic. "NoSQL" does not mean "no injection." It means the injection has a different shape, and because many developers believe document stores are immune, the bug is often left wide open in exactly the places you would never leave a SQL query unparameterized.
A 60-second MongoDB primer
You only need a little MongoDB to read every payload in this guide.
A document is a JSON-like record, for example a user with a username and a password field. A query is itself a JSON object describing what to match. To find a user you pass the driver an object like this:
{ "username": "alice", "password": "s3cr3t" }The database reads that as "match a document where username equals alice and password equals s3cr3t." The important part is that each value can be a plain scalar or an operator object. Instead of a literal value, you can supply:
{ "password": { "$ne": null } }$ne is the not-equal operator. This no longer means "password equals a string," it means "password is not equal to null." The operators you will reach for most:
$nenot equal$gt/$ltgreater than / less than$regexregular-expression match (with anchoring like^a)$inmatch any value in a list$existsfield presence
And the escape hatch: $where, which runs a snippet of server-side JavaScript to decide whether each document matches. That operator is where NoSQL injection gets its most dangerous variant, covered later.
The two flavours
NoSQL injection comes in two shapes, and keeping them straight tells you which payload to try.
- Operator injection. You supply an object where the app expected a string, and the object is an operator the driver trusts. This is the common case and the one behind almost every authentication bypass. See operator injection.
- Syntax injection. You break out of a server-side expression that the app built by gluing your input into a string, most often a
$whereJavaScript clause. This is rarer and depends on server-side JavaScript being reachable. See where injection.
Operator injection is about type. Syntax injection is about string concatenation. Most real findings are the first kind.
Why type confusion is the root cause
Here is the mechanism that makes operator injection so easy. In a JSON API, the body is parsed into an object and the app often passes a field straight into the query. If you can make that field arrive as an object instead of a string, you win. In a JSON request that is trivial, you just send an object.
But it also happens with ordinary form and query-string parameters. Many body parsers turn bracket notation into nested objects. A request like this:
username=admin&password[$ne]=xis parsed by libraries such as qs (what Express uses for extended URL-encoded bodies) into:
{ "username": "admin", "password": { "$ne": "x" } }The developer thought password was a string. The parser built an object. The driver saw an operator. That gap between "what the developer expected" and "what the parser produced" is the entire bug, and it is why query-string injection reaches endpoints that never knowingly accept JSON.
Authentication bypass
The headline attack. A login runs something like findOne({ username, password }) and trusts both fields are strings. Send the password as an operator and the check collapses:
{ "username": "admin", "password": { "$ne": null } }The password clause is now satisfied by the admin document whatever its real password is, so the query returns it and you are logged in without the password. Variants let you pick a specific account or walk the user list. The full set is in operator injection.
Data extraction
Operators do more than bypass logic, they read data. A filter you control plus $regex or $in lets you confirm field values, widen a result set, and pull records the UI never meant to show you. Data extraction covers smuggling operators into a filter to read other users' documents.
Blind and timing
Often nothing is reflected and the only signal is whether a login succeeds or a page changes. That is enough. An anchored $regex turns the yes/no response into a character-by-character oracle, and where server-side JavaScript is enabled a $where with sleep() gives you a timing oracle when there is no visible difference at all. See blind and timing NoSQL injection.
The $where escape hatch and server-side JavaScript
When an app builds a $where clause by concatenating your input into a JavaScript string, you can inject JavaScript that runs inside the database. That buys you logic bypass, data access, timing oracles, and in narrow cases more. Server-side JavaScript is enabled by default on a self-hosted MongoDB but is restricted on recent versions, commonly disabled as a hardening step, and deprecated as of MongoDB 8.0. Where injection covers the technique and frames its real, limited impact honestly.
A worked example
Say a login endpoint takes a JSON body and tells you only "welcome back" or "invalid credentials." You send a harmless pair of strings and get "invalid credentials." Now you send the password as $ne null and the response flips to "welcome back." Operator injection confirmed, and you are authenticated as the first matching user.
You want to log in as admin specifically, so you set the username to an anchored $regex of ^admin and keep the $ne password. Still "welcome back." Now you want the admin's actual password. The response only ever says welcome or invalid, so you go blind: set username to admin and the password to a $regex of ^a, then ^b, and so on. When the prefix matches the real password the response says "welcome back," and you have leaked the first character. Extend the anchor one character at a time and you reconstruct the whole password from nothing but a yes/no login response. That is the loop: confirm the type confusion, bypass the check, then turn the same operator into a read primitive.
Prevention
- Validate the type of every input before it reaches the query. A password is a string. If it arrives as an object or an array, reject the request. This single check kills operator injection.
- Do not pass raw request bodies into a query. Pull named fields and cast them to the type you expect.
- Disable server-side JavaScript (
--noscripting) and never build a$whereclause from user input. - Use a typed schema or validation layer on request bodies so an operator-shaped value never type-checks.
- Treat an ODM as a helper, not a shield. Mongoose only protects a field that is strictly typed and only if you do not hand it attacker-shaped objects.
The full per-driver guidance (Node, Python, PHP) is in preventing NoSQL injection.
How it compares to SQL injection
If you already know SQL injection, you know NoSQL injection. Both are untrusted input escaping the data context of a query. SQL injection breaks out of quoted text and reaches for UNION, error-based, and blind techniques. NoSQL injection changes the type of your input and reaches for operators and server-side JavaScript. The reconnaissance instinct, the blind-extraction patience, and the prevention principle (keep data as data) all transfer directly. The payloads are the only thing you relearn.
Where to go next
Work through the cluster in order, or jump to what you need. Start with NoSQL databases intro if the query model is new, then operator injection and query-string injection. When you want every payload in one place, the NoSQL injection cheat sheet has them. And when you are ready to break something real, sign up free and start hacking.