High CWE-943 OWASP A03:2021

NoSQL Injection in Vibe Coded Apps

How to find and fix operator injection in MongoDB, Firestore, and other NoSQL databases

Quick Answer

NoSQL injection exploits query operators in MongoDB, Firestore, and other NoSQL databases. Attackers pass objects like {"$ne": ""} instead of strings to bypass authentication. Ranked under OWASP A03:2021 Injection.

#3
OWASP Ranking
CWE-943
MongoDB
Most Affected
High
Severity

Source: OWASP Top 10 (2021) | CWE-943

What is NoSQL injection?

NoSQL injection is a vulnerability where attackers manipulate database queries by injecting operators or objects instead of expected string values. Unlike SQL injection which uses SQL syntax, NoSQL injection exploits JSON-based query operators like $ne, $gt, and $where.

Think of it like a hotel keycard system that accepts commands instead of just room numbers. Instead of entering "Room 203," an attacker types "all rooms where room number is not nothing" and gains access to every room.

According to OWASP Top 10 (2021), injection attacks rank #3 in web application security risks. CWE-943 specifically covers improper neutralization of special elements in data query logic. Many vibe coders assume "NoSQL means no injection," but this is a dangerous myth. The attack vector differs from SQL injection, but the consequences are equally severe.

How does NoSQL injection work?

NoSQL injection works by sending objects where strings are expected, exploiting query operators that databases interpret as commands. The most common attack targets authentication systems using MongoDB's comparison operators.

MongoDB Operator Injection

The classic NoSQL injection bypasses password checks using the $ne (not equal) operator:

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

// Normal request
{ "username": "admin", "password": "secret123" }

// Attack payload
{ "username": "admin", "password": { "$ne": "" } }
// This matches any password that is not empty - bypasses auth!

MongoDB $where Injection

The $where clause executes JavaScript, enabling code injection:

// Vulnerable code
db.collection('users').find({
  $where: `this.username == '${username}'`
})

// Attack payload
username = "'; return true; //"
// Executes: this.username == ''; return true; //'
// Returns ALL users in the database!

Firestore Path Injection

Firestore is vulnerable to path traversal when document IDs aren't validated:

// Vulnerable code
const doc = await db.collection('users').doc(userId).get()

// Normal request
userId = "user123"

// Attack payload
userId = "../secrets/apikeys"
// Could traverse to other collections depending on implementation

Why do AI tools generate vulnerable NoSQL code?

AI tools generate vulnerable NoSQL code because their training data includes insecure patterns, and NoSQL syntax looks deceptively safe compared to SQL.

Common AI-generated vulnerable patterns

When you ask AI tools for MongoDB authentication, they often generate code like this:

// Cursor, Bolt, Claude Code all generate this pattern
app.post('/login', async (req, res) => {
  const user = await db.collection('users').findOne({
    email: req.body.email,
    password: req.body.password  // No type checking!
  })
  if (user) {
    res.json({ token: generateToken(user) })
  }
})

This code works with normal requests but accepts objects like {"$ne": ""} in place of strings.

Why this happens:

  • Training data bias: AI models learn from codebases where NoSQL validation is often missing
  • No SQL keywords: Without obvious SQL syntax like SELECT or WHERE, the code looks safe
  • Working over secure: AI prioritizes code that runs without errors, not code that's secure
  • Operator injection is obscure: Less documentation exists compared to SQL injection

What could happen if I have NoSQL injection?

NoSQL injection can result in authentication bypass, data theft, denial of service, and in some cases server compromise through JavaScript execution.

  • Authentication bypass: Log in as any user without knowing their password using $ne operators
  • Data extraction: Use $regex operators to extract data character by character
  • Privilege escalation: Modify your own user document to grant admin privileges
  • Denial of service: Craft queries that consume excessive resources or crash the database
  • Server-side JavaScript execution: MongoDB's $where can execute arbitrary JavaScript

How do I detect NoSQL injection?

Detect NoSQL injection by searching for database queries that use user input without type validation. Any findOne(), find(), or updateOne() that directly uses req.body or req.query is suspicious.

Patterns to search for
// Direct use of req.body in queries (DANGEROUS)
.findOne({ email: req.body.email, password: req.body.password })

// No type checking before query (DANGEROUS)
const { username, password } = req.body
await collection.findOne({ username, password })

// $where with user input (DANGEROUS)
.find({ $where: `this.field == '${userInput}'` })

// Regex patterns to find these:
// \.find(One)?\s*\(\s*\{[^}]*req\.(body|query|params)
// \$where:\s*[`'"]

Don't want to search manually?

Scan your code free

How do I fix NoSQL injection?

Fix NoSQL injection by validating input types before queries. Ensure user input is a string (not an object), and never use $where with any user-influenced data.

AI Fix Prompt

Copy this prompt into Cursor, Claude Code, or Bolt to automatically fix NoSQL injection in your codebase:

Copy-paste this prompt
Fix all NoSQL injection vulnerabilities in my codebase. ## What to look for Search for these dangerous patterns in MongoDB/Firestore code: 1. User input passed directly to queries without type validation: - findOne({ field: req.body.value }) - find({ field: req.query.param }) - updateOne({ _id: req.params.id }, { $set: req.body }) 2. Use of $where with any user-influenced data: - $where: `this.field == '${userInput}'` - $where: function() { return this.field == variable } 3. Unvalidated document IDs or paths: - db.collection('users').doc(userId) without ID validation - Dynamic collection names from user input 4. Missing type checking on query parameters: - Accepting objects where strings expected - No validation before database operations ## How to fix ### For MongoDB - Add type validation: ```typescript // Before (vulnerable) const user = await db.collection('users').findOne({ email: req.body.email, password: req.body.password }) // After (secure) - Validate types first import { z } from 'zod' const loginSchema = z.object({ email: z.string().email(), password: z.string().min(8) }) const { email, password } = loginSchema.parse(req.body) const user = await db.collection('users').findOne({ email, password }) ``` ### For MongoDB - Use sanitization: ```typescript // Alternative: Use mongo-sanitize import sanitize from 'mongo-sanitize' const cleanEmail = sanitize(req.body.email) const cleanPassword = sanitize(req.body.password) const user = await db.collection('users').findOne({ email: cleanEmail, password: cleanPassword }) ``` ### For Firestore - Validate document IDs: ```typescript // Before (vulnerable) const doc = await db.collection('users').doc(userId).get() // After (secure) function isValidDocId(id: string): boolean { return typeof id === 'string' && /^[a-zA-Z0-9_-]+$/.test(id) } if (!isValidDocId(userId)) { throw new Error('Invalid document ID') } const doc = await db.collection('users').doc(userId).get() ``` ### Remove $where completely: ```javascript // Before (vulnerable) db.find({ $where: `this.name == '${name}'` }) // After (secure) - Use standard operators db.find({ name: name }) ``` ## Framework-specific notes - Express: Add validation middleware before route handlers - Next.js API routes: Validate in each route before database calls - Mongoose: Use schema validation, but still validate input types - Prisma with MongoDB: Use Prisma's type system, avoid raw queries ## After fixing 1. Search for remaining patterns: \$where|findOne.*req\.(body|query) 2. Add Zod or similar validation at API boundaries 3. List all files you modified with before/after snippets Please proceed systematically through my codebase.

Manual Fix

The key defense is type validation. Ensure user input is the expected type (string) before using it in queries.

VULNERABLE
// Direct use of req.body - DANGEROUS
app.post('/login', async (req, res) => {
  const user = await db.collection('users').findOne({
    username: req.body.username,
    password: req.body.password
  })
  // ...
})

// Attacker sends:
// { "username": "admin", "password": { "$ne": "" } }
// Result: Logs in as admin without knowing password
SECURE
// Type validation with Zod - SAFE
import { z } from 'zod'

const loginSchema = z.object({
  username: z.string().min(1).max(50),
  password: z.string().min(8)
})

app.post('/login', async (req, res) => {
  // This throws if input isn't a string
  const { username, password } = loginSchema.parse(req.body)

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

// Attacker sends { "$ne": "" } for password
// Result: Zod throws "Expected string, received object"

What changed: Zod validation ensures password must be a string. When an attacker sends an object like {"$ne": ""}, Zod rejects it before it reaches the database query. The malicious operator never gets a chance to execute.

Prevention methods by priority

1

Schema Validation (Zod)

Validate all input at API boundaries. Zod rejects objects where strings are expected.

const schema = z.object({
  email: z.string().email(),
  password: z.string()
})
2

Input Sanitization

Strip MongoDB operators from user input using mongo-sanitize or similar.

import sanitize from 'mongo-sanitize'
const clean = sanitize(req.body.email)
3

Avoid $where

Never use $where with user input. Use standard query operators instead.

// Use this instead of $where
db.find({ name: { $eq: name } })
4

Path Validation

For Firestore/document DBs, validate document IDs match expected patterns.

const isValid = /^[a-zA-Z0-9_-]+$/.test(id)

Frequently asked questions

What is NoSQL injection?

NoSQL injection is a vulnerability where attackers manipulate NoSQL database queries by injecting operators or objects instead of expected string values. Instead of SQL syntax, attackers use JSON operators like {"$ne": ""} or {"$gt": ""} to bypass authentication or extract data. It exploits the same fundamental weakness as SQL injection: trusting user input in database queries.

Is MongoDB vulnerable to injection?

Yes. MongoDB is vulnerable when queries accept user input without type validation. The most common attack uses operator injection, where attackers send {"$ne": ""} instead of a string password, matching any non-empty value. MongoDB's $where clause is also dangerous because it executes JavaScript. The fix is always validating that user input is the expected type before using it in queries.

How do I prevent NoSQL injection?

Prevent NoSQL injection by validating input types before queries. Use schema validation libraries like Zod to ensure user input is a string, not an object. Use query sanitization libraries like mongo-sanitize to strip dangerous operators. Never use $where with user input. For Firestore, validate document IDs match expected patterns before using them in paths.

What is the difference between SQL and NoSQL injection?

SQL injection exploits SQL syntax by injecting commands like OR 1=1 into queries. NoSQL injection exploits JSON operators and query syntax specific to each database. In MongoDB, attackers inject objects like {"$ne": ""} instead of strings. The concept is identical, trusting user input in queries, but the attack syntax differs. Both are prevented by parameterization and input validation.

Can Firestore be injected?

Firestore has different injection risks than MongoDB. The main threat is path traversal, where attackers manipulate document IDs to access other collections. If you use db.collection("users").doc(userId).get() without validating userId, an attacker could input "../secrets/apikeys" to traverse paths. Always validate document IDs match expected patterns like alphanumeric characters only.

Related content

Scan your code for NoSQL injection

Check your MongoDB and Firestore code for injection vulnerabilities and other security issues.

Try Vibeship Scanner