All posts

Field guide

NoSQL injection: how a crafted object bypasses your MongoDB login

NoSQL injection is SQL injection's document-database cousin. Instead of a crafted string, it uses a crafted object, and the classic case logs an attacker in as any user. Here is how it slips into AI-built apps and how to close it.

Last updated

NoSQL injection is the same kind of bug as SQL injection, untrusted input changing what a query means, but it exploits a different detail. Document databases like MongoDB accept query objects, so if an attacker can control the shape of the input, they can slip in operators that turn a precise lookup into one that matches almost anything.

The login-bypass case

The most striking example is a login that passes request fields straight into a query:

// the vulnerable pattern: req.body fields go straight into the query
const user = await User.findOne({
  email: req.body.email,
  password: req.body.password,   // expects a string
});
if (user) grantSession(user);

An attacker sends the password not as a string but as an object: { "$gt": "" }. The query now asks for a user whose password is greater than an empty string, which is true for nearly every user, so it returns a match and logs the attacker in. No password guessing required.

Want to see exactly what Prbl flags? Watch it scan a demo app, no repo or account needed.

See a live scan →

The fix: values, not operators

Make sure inputs are the type you expect, so a password is always a string, and validate the body's shape before querying:

const email = String(req.body.email);
const password = String(req.body.password);   // an object becomes "[object Object]"

const user = await User.findOne({ email });
if (user && await bcrypt.compare(password, user.passwordHash)) {
  grantSession(user);
}

Note the login is also structured to compare a hashed password rather than matching it in the query at all, which is the right pattern anyway. Coercing types, validating the body against a schema, and stripping keys that start with $ all help. The full walkthrough is in the fix for NoSQL injection.

Frequently asked questions

How is NoSQL injection different from SQL injection?

The idea is the same, untrusted input changing the meaning of a query, but the mechanism differs. SQL injection uses crafted strings; NoSQL injection often uses crafted objects. Because databases like MongoDB accept query objects, an attacker who can control the shape of the input can inject operators like $gt or $ne that turn a specific lookup into one that matches many records.

How does the login-bypass case work?

If your login passes the request body straight into a query, an attacker sends an object like { "$gt": "" } as the password instead of a string. The query then asks for a user whose password is greater than an empty string, which is true for essentially every user, so the database returns a match and the attacker is logged in. The fix is to make sure the input is treated as a plain value, not an operator object.

Why do AI tools introduce it?

Because passing req.body fields directly into a query is the shortest way to make a login or search work, and it behaves correctly with normal string input. The assistant has no signal that a client might send an object where a string is expected, so it ships the version that trusts the input's shape.

What is the fix?

Coerce inputs to the type you expect before querying, so a password is always a string and never an operator object, and validate the request body against a schema so unexpected shapes are rejected. Many teams also enable a sanitizer that strips keys starting with $ from user input. The goal is that the query receives values, not query operators the user chose.

Check your queries

Any query built from request fields without type coercion is worth a look, and a scan flags the pattern. Run a free scan and see whether your app trusts the shape of its input.

Prbl scans your live app or your codebase for exactly the kinds of issues above.

NoSQL Injection: How a Crafted Object Bypasses Your MongoDB Login: Prbl