NoSQL Injection: The Complete Practical Guide to MongoDB Injection
NoSQL Databases and Why They Are Injectable
An intro to how MongoDB queries work as objects, the operators you abuse, and why no SQL does not mean no injection. The foundation for NoSQL injection.
Published
Before you can exploit NoSQL injection you need a clear picture of how the database reads a query, because the whole bug lives in a gap between what the developer thinks they sent and what the driver actually runs. This page builds that picture for MongoDB, the store behind almost every NoSQL injection you will find on a web target. No prior MongoDB experience needed. Want to practice as you read? Sign up free.
Documents instead of rows
A relational database stores rows in tables with a fixed set of typed columns. A document database stores documents, which are JSON-like records, in collections. A user document might look like this:
{
"_id": "6531a0b2e13f",
"username": "alice",
"password": "s3cr3t",
"role": "user"
}There is no rigid table schema forcing password to be a string. Documents in the same collection can have different shapes. That flexibility is convenient for developers and, as you will see, convenient for attackers, because the database is perfectly happy to compare a field against something that is not a plain string.
A query is an object, not a string
This is the single most important idea in the whole topic. In SQL you hand the database a string of query text:
SELECT * FROM users WHERE username = 'alice' AND password = 's3cr3t'In MongoDB you hand the driver an object that describes what to match:
{ "username": "alice", "password": "s3cr3t" }The database reads that as "find a document where username equals alice and password equals s3cr3t." There is no query string to parse, no quotes around values, nothing to escape. The query is already structured data. That is why classic SQL injection payloads do nothing against MongoDB, and it is also why a different and equally serious injection class exists.
Values can be operators
Each field in a query object can hold a plain value, like the string alice, or an operator object that tells the database how to match. An operator object is just a nested object whose key starts with a dollar sign. Instead of "password equals this string" you can say "password is not equal to null":
{ "password": { "$ne": null } }The database treats $ne as an instruction, not as data. This is the lever behind operator injection: if an attacker can make a field arrive as an operator object instead of a plain value, they stop comparing a value and start giving the database instructions.
The operators you will use constantly:
$nematches anything not equal to the given value.$nenull matches every document whose field is not null, which also includes documents that do not have the field at all.$gtand$ltmatch greater-than and less-than.$gtwith an empty string matches any non-empty string value. MongoDB comparisons are type-bracketed, so this matches string-typed values only, which is still "match anything" for a field that holds a string like a password.$regexmatches a regular expression. Anchor it with^and you can test a value one character at a time, which is the heart of blind extraction.$inmatches any value in a list, handy for trying several usernames at once.$existsmatches on whether a field is present at all.$whereruns a snippet of JavaScript per document to decide the match. This is the most powerful and most dangerous operator, and it is covered in its own page.
Where your input meets the query
An injection needs a place where attacker input flows into that query object. In a typical web app, the request body or query string is parsed into an object and the app pulls fields out of it to build the query. The vulnerable pattern looks like this in Node:
// Express + Mongoose or the native driver
const user = await users.findOne({
username: req.body.username,
password: req.body.password,
});The developer assumes req.body.username and req.body.password are strings. Nothing enforces that. If the request body is JSON, the client fully controls the type of each field and can send an object. Even with a classic form body, a bracket-notation parameter can be parsed into a nested object. Either way, the untrusted value lands in the query with its attacker-chosen type intact, and the database obeys whatever operator it finds.
Why "no SQL" does not mean "no injection"
The phrase NoSQL is marketing for "not only SQL," not a promise of safety. The injection class is the same one you know from SQL: untrusted input changing the meaning of a query. What changed is only the grammar. There is no quote to escape, so the exploit is not about breaking out of a string. It is about controlling the type of your input. Send an object where a string was expected, and the query stops meaning what the developer wrote.
Developers who believe document stores are immune often skip the one defense that matters, checking the type of each input, which is exactly why this bug is so common in real code. The mitigation is not a special library, it is refusing to let a field that should be a string arrive as an object.
What is next
You now have everything you need to read the attacks. Start with operator injection, the authentication bypass that falls straight out of the type confusion you just learned, then see how the same payloads reach ordinary endpoints in query-string injection. Or jump on a free live target and try an operator against a login right now.
← Back to NoSQL Injection: The Complete Practical Guide to MongoDB Injection