Application Security

NoSQL Injection and MongoDB: Prevention Guide

NoSQL injection attacks exploit the query languages of non-relational databases to bypass authentication, extract data, and modify records. This guide focuses on MongoDB injection with defenses applicable to all NoSQL databases.

Yukti Singhal
Security Engineer
6 min read

The shift from SQL to NoSQL databases did not eliminate injection attacks. It changed them. SQL injection uses string concatenation to manipulate queries. NoSQL injection uses operator injection, JSON manipulation, and JavaScript execution to achieve the same goals: authentication bypass, data extraction, and unauthorized modification.

MongoDB is the most widely deployed NoSQL database and the most frequent target. Its query language uses JSON-like documents with operators, and when user input is incorporated into queries without sanitization, attackers can inject operators that change the query's logic entirely.

Basic MongoDB Injection

Consider a login endpoint that queries MongoDB:

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

If the application parses the request body as JSON (common in Express.js with body-parser), an attacker can send:

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

The $gt operator means "greater than." Every non-empty string is greater than an empty string, so this query matches the first user in the collection -- typically the admin. The attacker logs in without knowing any credentials.

Other useful operators for injection:

  • $ne (not equal): {"password": {"$ne": ""}} matches any non-empty password
  • $regex: {"username": {"$regex": "^admin"}} matches usernames starting with "admin"
  • $exists: {"password": {"$exists": true}} matches any document with a password field
  • $in: {"role": {"$in": ["admin", "superadmin"]}} matches specific roles
  • $where: Executes JavaScript, enabling arbitrary logic

Authentication Bypass Patterns

MongoDB injection in authentication flows is remarkably common because the attack is simple and the impact is immediate:

Operator injection in login. As shown above, replacing string values with operator objects bypasses credential checks. This works when the application accepts JSON input and passes it directly to the MongoDB query.

$regex for credential enumeration. An attacker can extract usernames and passwords character by character:

{"username": {"$regex": "^a"}, "password": {"$gt": ""}}

If this returns a user, the first character of a username is "a." Continue with ^ab, ^ac, etc., to extract the full username. The same technique works for password hashes if the application leaks timing or response differences.

$where injection. If the application constructs $where clauses with user input:

db.collection('users').findOne({
  $where: `this.username == '${username}'`
});

The attacker supplies ' || 1==1 || ' as the username, resulting in a $where clause that always evaluates to true.

Prevention:

  • Never pass user input directly as query values -- always validate the type
  • Reject objects when a string is expected: if (typeof req.body.username !== 'string') return
  • Use a schema validation library (Joi, Yup, Zod) to enforce input types
  • Avoid $where entirely -- it executes JavaScript and is slow and dangerous

Data Extraction Techniques

Beyond authentication bypass, NoSQL injection enables data extraction:

Blind extraction using $regex. Even when the application does not return query results directly, an attacker can extract data through boolean-based blind injection. Test each character position with regex patterns and observe whether the response indicates a match.

Error-based extraction. Some MongoDB error messages include parts of the query or document data. Crafting queries that trigger specific errors can leak information.

Aggregation pipeline injection. If user input reaches a MongoDB aggregation pipeline, the attacker can inject stages like $lookup to join data from other collections, $out to write query results to a new collection, or $merge to modify existing collections.

$lookup for cross-collection access:

{
  "$lookup": {
    "from": "admin_users",
    "localField": "username",
    "foreignField": "username",
    "as": "admin_data"
  }
}

If the attacker can inject an aggregation stage, they can access any collection in the database.

Server-Side JavaScript Injection

MongoDB supports server-side JavaScript execution through several mechanisms:

$where clauses. As discussed, $where evaluates JavaScript for each document. Injection in $where gives arbitrary JavaScript execution in the database context.

MapReduce. The mapReduce command executes JavaScript map and reduce functions. If user input reaches these functions, the attacker can execute arbitrary JavaScript.

$function (MongoDB 4.4+). The $function aggregation operator executes JavaScript. Input reaching $function allows arbitrary code execution.

Impact of server-side JS execution:

  • Read any data in any collection
  • Modify or delete data
  • Denial of service through resource-intensive operations
  • In older MongoDB versions, access to the filesystem (this has been removed in modern versions)

Prevention:

  • Disable server-side JavaScript execution if not needed: set security.javascriptEnabled: false in MongoDB configuration
  • Never construct $where clauses from user input
  • Avoid MapReduce in favor of the aggregation pipeline
  • Audit all code paths that construct aggregation pipelines for injection

Other NoSQL Databases

While MongoDB is the focus, NoSQL injection affects other databases:

Redis. Redis commands are sent as arrays of strings. If user input is concatenated into commands, attackers can inject additional commands. The EVAL command executes Lua scripts, which is especially dangerous.

CouchDB. CouchDB uses JavaScript views and map/reduce functions. Injection in view definitions or query parameters can execute arbitrary JavaScript.

DynamoDB. AWS DynamoDB uses condition expressions with a specific syntax. Injection in condition expressions can bypass access controls, though the impact is more limited than MongoDB.

Cassandra. CQL (Cassandra Query Language) is similar enough to SQL that traditional injection techniques apply. Parameterized queries prevent CQL injection.

Secure Coding Patterns

Input type validation. The single most effective defense. Ensure every user input is the expected type before using it in a query:

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

Schema validation. Use a validation library to define expected input shapes:

const schema = Joi.object({
  username: Joi.string().alphanum().min(3).max(30).required(),
  password: Joi.string().min(8).required()
});

Parameterized queries. When using MongoDB drivers, pass values as parameters rather than constructing query objects from raw input. Most MongoDB drivers handle this correctly when used as intended.

Mongoose schema enforcement. If using Mongoose, define schemas with strict types. Mongoose will cast inputs to the defined types or reject them:

const userSchema = new Schema({
  username: { type: String, required: true },
  password: { type: String, required: true }
});

Least-privilege database users. Connect your application with a database user that has only the permissions it needs. A read-only user cannot be used for data modification even if injection occurs.

Monitoring and Detection

  • Log all database queries in development to identify injection-vulnerable patterns
  • Monitor for unusual query operators in production logs
  • Alert on queries containing $where, $function, or unexpected aggregation stages
  • Track query performance -- injected queries often have different performance characteristics
  • Use MongoDB's profiler to identify slow or unusual queries

How Safeguard.sh Helps

Safeguard.sh monitors your MongoDB drivers, ODM libraries, and database middleware for known injection vulnerabilities. When a CVE is published for a MongoDB driver that enables operator injection or bypasses type checking, Safeguard.sh flags it in your SBOM and alerts your team. By providing continuous visibility into the security posture of your database access layer, Safeguard.sh helps ensure that the components between your application and your database are not the weak link in your injection defenses.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.