Web Security

What Is NoSQL Injection? MongoDB and NoSQL Injection Explained

A complete technical guide to NoSQL injection: how MongoDB and other NoSQL databases handle injection differently from SQL, and how to detect, verify, and prevent it.

Intermediate12 min·Nyxeara Security Research·2026-09-16·CWE-943
nosql-injectionweb-securitydatabase-securitymongodb
Prerequisites
what-is-httpwhat-is-a-database-query

Short answer

NoSQL injection occurs when user input is interpreted as part of a query operator or condition structure — rather than as a plain value — because the application builds query objects using unvalidated input. Unlike SQL injection, the attacker is often injecting operators ($gt, $ne, $regex) into a structured query object instead of altering string syntax.

The idea in one minute

Imagine a hotel key-card system that reads your key and opens a room based on the pattern "room-[NUMBER]." You insert your card, the system checks your number against the registry, and unlocks the corresponding door. Now suppose the system doesn't lock down what "NUMBER" can be. Instead of inserting a room number, you insert a pattern: *. The system interprets * as "any room," slides the bolt on every door in the hotel, and your single key-card now works everywhere.

A MongoDB query object behaves similarly. The application builds a filter like { "username": input } expecting a plain string. But if the application passes the input directly into a query — for example, parsing JSON from the request body — an attacker can submit { "$ne": "" } instead of a string. The application meant to check "does a user exist with this exact username?" and instead executes "does a user exist whose username is not empty?" — which is almost certainly true. The single condition that was supposed to narrow down one record now matches every record in the collection.

How NoSQL injection actually works

A typical vulnerable login query in MongoDB is constructed by taking JSON from the request body and passing it straight into a query:

// Vulnerable: direct passthrough of parsed body
app.post('/login', (req, res) => {
  const query = {
    username: req.body.username,
    password: req.body.password
  };
  const user = db.collection('users').findOne(query);
  if (user) {
    // authentication succeeds
  }
});

If the application parses req.body as JSON, an attacker can send:

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

The query becomes:

db.users.findOne({ "username": { "$ne": "" }, "password": { "$ne": "" } })

Both conditions are $ne ("not equal to empty string") — which is true for any real username and password. The query returns the first user in the collection, granting the attacker a logged-in session as that user.

The same principle applies to Express.js applications that use express.urlencoded() or body-parser with the extended: true option, which parses nested objects from form-encoded data. A form field named username[$ne] with value `` (empty) produces the same query operator injection.

Attackers can also inject $regex to extract data character by character — the blind NoSQL equivalent of boolean-based SQL injection:

{
  "username": "admin",
  "password": { "$regex": "^a.*" }
}

If the response differs for ^a.* versus ^b.*, the attacker can walk through every character of the password field without ever reading it directly.

There are three primary exploitation categories in NoSQL injection:

| Category | How it's confirmed | Example technique | |---|---|---| | Operator injection | Input is parsed as a query operator ($ne, $gt, $regex) instead of a value | JSON body with { "$ne": "" } | | Syntax injection | Input contains special characters that break a string-based query built via concatenation | URL parameter with '; return true; // | | Blind regex extraction | No data in the response, but a $regex pattern produces observable behavior differences | { "password": { "$regex": "^a" } } vs "^b" |

The request flow

sequenceDiagram
    participant Attacker
    participant App as Vulnerable Application
    participant DB as MongoDB / NoSQL

    Attacker->>App: JSON/form input with operator syntax ($ne, $regex)
    App->>DB: Query object containing injected operator
    DB-->>App: Unintended documents matched by operator
    App-->>Attacker: Auth bypass, data leak, or behavioral signal

A minimal example

Vulnerable login in a Node.js / Express application:

// Vulnerable: user-controlled object injected into query
app.post('/login', async (req, res) => {
  const user = await db.collection('users').findOne({
    username: req.body.username,
    password: req.body.password
  });

  if (user) {
    res.json({ success: true, user: user.username });
  } else {
    res.status(401).json({ error: 'Invalid credentials' });
  }
});

An attacker sends:

{
  "username": { "$gt": "" },
  "password": { "$gt": "" }
}

$gt ("greater than") compares against the empty string. Every non-empty username and password satisfies "value" > "", so findOne returns the first document in the collection — typically an administrative user if that collection is sorted by insertion order.

The fix is to enforce that username and password are primitive strings before they reach the query:

// Fixed: coerce and validate types before query building
app.post('/login', async (req, res) => {
  const { username, password } = req.body;

  if (typeof username !== 'string' || typeof password !== 'string') {
    return res.status(400).json({ error: 'Invalid input types' });
  }

  const user = await db.collection('users').findOne({
    username: username,
    password: password
  });

  // ...
});

Here, submitting { "$ne": "" } fails the typeof check, because { "$ne": "" } is an object, not a string. The query never receives an operator — only flat string values.

Why the vulnerability exists

  • The application parses JSON request bodies or uses extended: true URL encoding and passes parsed objects directly into query filters — a pattern particularly common in Node.js / Express applications using the mongodb or mongoose drivers
  • Server-side code does not validate that submitted values are primitive types (string, number) before inserting them into query objects, allowing nested operator objects to pass through
  • The MongoDB query language treats objects as operator definitions when they appear in value positions — { "field": { "$gt": 0 } } is not a comparison of a field against a nested object; it is a directive to check for values greater than zero. This is by design, which makes the API inherently sensitive to unvalidated structured input
  • Code reviews focus on string escaping (as in SQL injection) and overlook the operator-level injection surface because there is no concatenation to flag

What attackers look for

  • Login and authentication endpoints that accept JSON and likely pass request body fields directly into queries
  • Search, filter, and autocomplete features where the query might use $regex or $text operators to match against user input
  • API endpoints that pass through nested object structures from client requests — common in REST APIs built with Express and body-parser
  • URL parameters that appear in query conditions, especially when the framework enables nested parameter parsing (username[$gt]=)
  • Password reset, email lookup, and "forgot username" forms — anywhere a single-field lookup returns a yes/no answer that can be toggled via operator injection
  • Any response that echoes back user data or includes an error message that reveals the query shape (e.g., a MongoDB error mentioning $regex or an unexpected field name)

Detection

  • Code review: look for patterns where req.body, req.query, or deserialized JSON is used directly inside find(), findOne(), aggregate(), or updateOne() — especially without type checks or schema validation
  • Manual testing with JSON payloads: submit a login request where the password field is { "$ne": "" } instead of a string. If authentication succeeds with an arbitrary password, operator injection is confirmed
  • Blind regex extraction: for password-reset or user-lookup endpoints, send username[$regex]=^a as a URL-encoded parameter alongside a known username. If the response differs from username[$regex]=^z, the endpoint is injecting the parameter into a $regex query
  • Error-based detection: send malformed operator syntax like { "$foo": "bar" } and observe whether the response leaks a MongoDB error message such as "unknown operator: $foo" or "Can't canonicalize query"

Verification: real vulnerability or false positive?

Confirmed when you can demonstrate that injected operator syntax changes the query's behavior deterministically:

  • Submitting { "$ne": "" } for a password field succeeds where an empty string or incorrect string does not — repeatable across sessions, not a race condition or cached result
  • Two $regex patterns that should differ only by a single character produce reliably different responses, and the boundary between "match" and "no match" is consistent
  • The injection works only with valid MongoDB operators ($ne, $gt, $regex, $where, $exists) and not with random object keys — confirming the parser is interpreting them as operators, not passing them through as literal values
  • A $where operator payload, which executes arbitrary JavaScript, produces a timing difference that matches the injected logic

A single successful login with a weird password is not enough — operator injection must be confirmed by demonstrating that the injected structure, not coincidental credential validity, produced the result. Toggle between { "$ne": "" } and { "$eq": "nonexistent" } and show the behavior flips.

Real-world impact

NoSQL injection can match the severity of SQL injection in environments where MongoDB or similar databases handle sensitive data:

  • Authentication bypass: the most common outcome — logging in as any user (often the first in the collection, which tends to be an administrator) without knowing their password, using $ne or $gt operators
  • Data extraction via blind regex: walking through character-by-character $regex patterns to extract password hashes, session tokens, or personal data from fields that are never returned in the response, using only binary yes/no signals
  • JavaScript injection via $where: the $where operator executes arbitrary JavaScript in the database engine. An attacker can inject { "$where": "sleep(5000)" } to cause time-based delays, or craft more destructive JavaScript that reads other documents and modifies fields — escalating injection to code execution within the database process
  • Privilege escalation: once authenticated as another user via operator injection, the attacker inherits that user's roles, permissions, and access to downstream resources — the injection becomes a lateral movement vector
  • Real attacks against production MongoDB-backed applications have demonstrated that a single unvalidated JSON field in a password reset endpoint can expose the entire user collection through automated $regex extraction, requiring no authentication at all

Prevention

  • Type enforcement on input values: before using any user-supplied value in a query, confirm it is a primitive string, number, or boolean — reject objects, arrays, and null. In JavaScript, typeof value === 'string' is a sufficient gate against operator injection
  • Schema validation at the application layer: use libraries like joi, zod, or express-validator to validate the shape and types of request bodies before they reach query construction. Reject any field whose value is not the expected type
  • Avoid extended: true on URL-encoded body parsers: when using Express body-parser or express.urlencoded(), set extended: false to prevent nested object parsing from form-encoded parameters like username[$ne]=
  • Parameterize queries using typed driver methods: use positional parameters where the driver expects primitive placeholders, not raw object injection. MongoDB drivers support findOne({ username: ? }, [username]) style parameterization that prevents operator injection
  • Disable $where if not required: MongoDB's $where operator runs arbitrary JavaScript. Disable it at the database role or application connection level unless the application explicitly depends on it
  • Keep ORM/ODM abstractions in place: Mongoose schemas enforce field types at the model level — bypassing them with Model.find({ ...rawInput }) bypasses the safeguard. Treat any direct Collection access as an exception requiring review

Related vulnerabilities

  • SQL injection — the same root cause (user input interpreted as query structure) but through string concatenation rather than operator injection into structured objects
  • Command injection — user input altering a shell command's structure, analogous to how operator injection alters a query's semantics without changing its syntax
  • LDAP injection — structured query injection into directory service filters, similar in that special characters or operators change the filter's matching behavior
  • Mass assignment — often paired with NoSQL injection; sending extra fields in a request body that get written to the database, not just read from it

Testing methodology (do this safely)

  • Only test targets you're authorized to test — NoSQL injection can modify data if the injection point is in an updateOne() or deleteOne() call, not just find().
  • Start with read-only operator injection ($ne, $gt) before attempting $regex extraction or $where JavaScript execution.
  • For $regex extraction, test with single-character boundaries (^a vs ^b) and stop at the first character — do not automate a full extraction unless explicitly authorized to extract that data.
  • Never inject $where payloads containing sleep(), while loops, or unbounded operations in a production environment — these consume database resources and affect other users.
  • Document the exact payload, the affected field, and the response differential. Confirm the behavior reverses when the operator is removed before reporting.
  • When testing extended: true URL-encoded forms, send username[$gt]= as a query or body parameter and observe whether the normal response changes — a clean boolean signal.

Further reading

Nyxeara perspective

NoSQL injection is often underestimated precisely because it does not look like SQL injection. Security reviews that scan for string concatenation miss it entirely — the vulnerability lives in a trust boundary between the parsed HTTP body and the database driver's operator interpretation, not in a string. Treating any user-controlled value as a candidate for injection — whether it reaches a query through concatenation or through object assignment — is what separates thorough detection from a false sense of coverage. A JSON body parser is not a sanitizer.

Published 2026-09-16 · Updated 2026-09-16