đŸ›Ąïž Application Security CheatSheet

NoSQL Injection Deep Dive

NoSQL Injection happens when an application builds a NoSQL query from untrusted input in a way that lets the attacker change the meaning of the query. Instead of input being treated as simple data (like a username), it becomes part of the query structure (like operators, filters, or conditions).

In the wild: the scary part isn’t the obvious payload — it’s the one query nobody reviewed because it lives in a background job or a report export.

Key idea: NoSQL is not “safe by default.” If user input can influence query operators or query shape, you can get NoSQL Injection.

Why it exists (root cause)

Interview line: “Root cause is letting untrusted input control query structure—operators, nesting, or types—not just query values.”

Mental model: value control vs query-shape control

What the user controlsRiskWhy
Only a primitive value (string/number) assigned to a fixed fieldLowerInput stays “data” if typed/validated
A JSON object merged into the filterHighUser can influence operators, nesting, and logic
Field names or sort/where clauses from user inputHighUser can redirect query to unintended fields/paths
Rule of thumb: keep query shape server-controlled, and keep user input limited to validated, typed values.

Where NoSQL Injection commonly appears

Node.js examples (Express + Mongo-style)

Vulnerable pattern (unsafe query object merge)

// ❌ Vulnerable: merges attacker-controlled JSON into the query filter
import express from "express";
import bodyParser from "body-parser";
import { MongoClient } from "mongodb";

const app = express();
app.use(bodyParser.json());

app.post("/api/users/search", async (req, res) => {
  const filter = req.body.filter;            // attacker-controlled object
  const sort = req.body.sort || { createdAt: -1 };

  // Danger: filter may contain operators/paths the server didn't intend
  const users = await req.db.collection("users")
    .find(filter)
    .sort(sort)
    .limit(50)
    .toArray();

  res.json({ users });
});

Fixed pattern (server-controlled shape + validation + typing)

// ✅ Secure: allow-list fields + coerce types + block operator keys
function isPlainObject(x) {
  return x && typeof x === "object" && !Array.isArray(x);
}

function rejectOperatorKeys(obj) {
  // Block any key that starts with "$" or contains "." to avoid operator/path abuse
  for (const k of Object.keys(obj)) {
    if (k.startsWith("$") || k.includes(".")) return false;
    const v = obj[k];
    if (isPlainObject(v) && !rejectOperatorKeys(v)) return false;
  }
  return true;
}

app.post("/api/users/search", async (req, res) => {
  const q = String(req.body.q || "").trim();
  const page = Number(req.body.page || 1);
  const pageSize = Math.min(Number(req.body.pageSize || 20), 50);

  // Server controls the query SHAPE; user controls only values
  const filter = {};
  if (q) {
    // Example: allow searching by email prefix only (business-defined)
    filter.email = { $regex: "^" + q.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), $options: "i" };
  }

  // If you accept structured filters, validate strictly:
  const raw = req.body.filter;
  if (raw !== undefined) {
    if (!isPlainObject(raw) || !rejectOperatorKeys(raw)) return res.status(400).json({ error: "Invalid filter" });

    // Allow-list only specific fields, and keep values typed as primitives
    const allowed = ["status", "role"];
    for (const f of allowed) {
      if (raw[f] !== undefined) filter[f] = String(raw[f]);
    }
  }

  const users = await req.db.collection("users")
    .find(filter)
    .skip((page - 1) * pageSize)
    .limit(pageSize)
    .toArray();

  res.json({ users });
});
experienced note: Prefer schema validation (e.g., JSON Schema / Zod) and strict allow-lists. The goal is to prevent user-controlled operators and paths.

Exploitation progression (attacker mindset)

This describes how attackers think at a high level. It avoids step-by-step exploit instructions.

Phase 1: Look for query flexibility

Phase 2: Identify query-shape control

Phase 3: Expand impact conceptually

Defensive takeaway: if you keep query shape server-controlled and block operators/paths, you cut off most NoSQL injection paths.

Tricky edge cases & bypass logic (conceptual)

Safe validation & testing guidance (defensive verification)

Professional rule: focus on demonstrating unsafe query-shape control and its security impact (data exposure / auth bypass) using minimal, safe evidence.

Fixes that hold in production

1) Keep query shape server-controlled

2) Block operators and path keys

3) Validate and type inputs

4) Secure authorization filters

Confidence levels (low / medium / high)

Interview-ready summaries (60-second + 2-minute)

60-second answer

NoSQL Injection is when untrusted input changes the structure or meaning of a NoSQL query. It often happens when apps merge user JSON directly into a query filter or allow operator keys. I validate by tracing input into the database filter and confirming query-shape control. Fixes are strict schema validation, allow-listing fields, blocking operator/path keys, and enforcing server-side authorization scoping.

2-minute answer

I treat NoSQL Injection as the same class as Injection: untrusted input reaching an interpreter—in this case, the NoSQL query parser. The key difference is that NoSQL drivers often accept rich query objects, so the main risk is user-controlled query shape: operators, nesting, types, and paths. This often leads to broadening result sets, bypassing business checks, or breaking tenant isolation. I look for unsafe merges of request JSON into filters, type confusion, and operator/path keys. The durable remediation is to keep query shape server-controlled, validate and type all inputs, block operator and dot-path keys, and always enforce immutable server-side auth filters like tenantId/ownerId.

Checklist (quick review)

Remediation playbook

  1. Contain: disable “advanced filter JSON” or restrict it to trusted admins temporarily.
  2. Inventory: identify all endpoints accepting filter objects, sort/projection objects, and query builders.
  3. Fix boundary: replace raw merge patterns with server-constructed filters.
  4. Validate: apply strict schemas; reject operator keys and dot-paths; enforce primitive types.
  5. Authorize: inject immutable scoping (tenant/owner) based on the session and keep it non-overridable.
  6. Guardrails: enforce page size limits, query timeouts, and safe regex usage; add audit logs for filter usage.
  7. Prevent regressions: add tests and CI checks that forbid passing request objects directly to DB query APIs.

Interview Questions & Answers (Easy → Hard)

Easy

  1. What is NoSQL Injection?
    A: Plain-English: it’s when user input changes the meaning of a NoSQL query. Deeply, it’s untrusted input influencing query shape (operators/types/paths) rather than only values.
  2. Is NoSQL “safe from injection” compared to SQL?
    A: Plainly, no. Deeply, NoSQL often uses query objects, so unsafe merges or operator exposure create injection risk.
  3. What’s the most common developer mistake?
    A: Plainly, using request JSON as the filter. Deeply, merging attacker-controlled objects into find()/where() so operators or nested conditions become attacker-controlled.
  4. What’s the single best defense?
    A: Plainly, keep query shape server-controlled. Deeply, allow-list fields, block operator/path keys, and validate types strictly.
  5. Why do operator keys matter?
    A: Plainly, they change logic. Deeply, operators let the query behave differently than “field equals value,” which can broaden matches or bypass checks.
  6. What’s a safe way to accept filters?
    A: Plainly, accept only known fields. Deeply, map request inputs to an allow-listed server-built filter and reject unknown structures.

Medium

  1. Scenario: A search endpoint accepts {"filter": {...}}. What do you check first?
    A: Plainly, whether it’s merged into DB queries. Deeply, trace if filter goes directly to find() and whether operator/path keys are blocked and types are enforced.
  2. Scenario: A login endpoint uses a Mongo query built from JSON body fields.
    A: Plainly, that can allow logic manipulation. Deeply, ensure the code enforces primitives and never accepts objects for credential fields; use explicit comparisons and strict schema validation.
  3. Follow-up: What evidence makes it “high confidence”?
    A: Plainly, proof input changes query semantics. Deeply, code-level trace to unsafe query merge plus repeatable behavior change attributable to query-shape control.
  4. Scenario: User controls sort or projection objects.
    A: Plainly, that can leak data. Deeply, projection can expose sensitive fields; sort can cause heavy scans. Allow-list both and keep defaults safe.
  5. Follow-up: How do you secure tenant isolation in queries?
    A: Plainly, enforce it on the server. Deeply, inject tenantId/ownerId constraints from session context and prevent clients from widening or overriding them.
  6. Scenario: The app supports regex search.
    A: Plainly, regex can be abused. Deeply, only allow safe patterns (e.g., prefix match), escape user input, and enforce limits to avoid expensive queries.
  7. Follow-up: How do you avoid performance-based abuse?
    A: Plainly, limit results. Deeply, enforce max page size, require indexes, apply timeouts, and restrict unbounded sorts/projections/regex.

Hard

  1. Scenario: You fixed operator keys but still accept arbitrary field names.
    A: Plainly, still risky. Deeply, field-name control can target sensitive nested fields or bypass intended constraints; allow-list fields and block dot-paths.
  2. Scenario: Filters are stored in DB and executed later by a worker.
    A: Plainly, that’s second-order injection. Deeply, stored untrusted query objects can bypass front-end validation; enforce schema validation at execution time too.
  3. Follow-up: What’s the trade-off between flexibility and security for filtering?
    A: Plainly, more flexibility increases risk. Deeply, safe design uses a restricted query DSL with allow-listed operators and fields, plus strong typing and limits.
  4. Scenario: GraphQL resolver passes args directly into Mongo queries.
    A: Plainly, it can become NoSQL injection. Deeply, treat GraphQL args as untrusted; map them to allow-listed fields/operators and validate types and limits.
  5. Follow-up: How would you create regression prevention?
    A: Plainly, CI + tests. Deeply, ban passing request objects into query APIs, add lint/grep checks, and write tests ensuring operator/path keys are rejected.
  6. Scenario: “We sanitize by deleting keys that start with $.” Is that enough?
    A: Plainly, not always. Deeply, dot-paths and type confusion can still alter semantics; you need a strict schema and allow-list design, not ad-hoc sanitization.
  7. Follow-up: Where does NoSQL Injection map in OWASP?
    A: Plainly, Injection. Deeply, it maps to OWASP Top 10 A03 Injection, often enabled by insecure design (A04) and missing validation (A05 patterns).
  8. Scenario: Client can pass tenantId as part of filter.
    A: Plainly, that’s dangerous. Deeply, tenant scoping must be derived from the authenticated session and be immutable; never trust client-provided tenantId for access control.