Mesrai
Back to blog
// essaySecurity

NoSQL Injection in MongoDB Queries: How Mesrai Spots $where Abuse

Real PR walkthrough: MongoDB `$where` with user input is server-side JS execution. Mesrai's review comment, the attack, the fix — CWE-943 in detail.

Mesrai TeamJuly 17, 20269 min read

This is a real Mesrai review on a Mongo-backed search endpoint. The team was using the `$where` operator to support a 'contains-style' lookup. The PR was small. The bug was severe enough to bring the server down with a single request.

The vulnerable diff

tsapi/search-users.ts
// api/search-users.ts — original PR
import { Router } from "express";
import { db } from "../db";

export const searchUsersRouter = Router();

searchUsersRouter.get("/users", async (req, res) => {
  const q = req.query.q as string;
  if (!q) return res.status(400).json({ error: "q required" });

  // BUG: $where evaluates the string as JavaScript on the server
  const users = await db.users.find({
    $where: `this.username == '${q}'`,
  });
  return res.json({ users });
});

What is wrong

The `$where` operator in MongoDB accepts a JavaScript expression and evaluates it on the server, in the database process, against every document considered. When user input is interpolated into that expression, the user controls arbitrary JavaScript execution — the same security model as an `eval()` of untrusted input. This is CWE-943 (Improper Neutralization of Special Elements in Data Query Logic) and the MongoDB documentation has carried a warning against this pattern for over a decade.

The attack

With one crafted query string, an attacker takes down the database thread pool:

bashexploit.sh
# Single request, infinite loop server-side
curl 'https://api.example.com/users?q=x%27%3B%20while(true)%20%7B%7D%3B%20var%20y%20%3D%20%27x'
# Decoded q: x'; while(true) {}; var y = 'x

The resulting `$where` string becomes `this.username == 'x'; while(true) {}; var y = 'x'` — a hot loop that hangs the database worker. Repeat the request and you have a one-shot denial of service. Data exfiltration is also possible — the JavaScript context has access to document fields.

Mesrai's review comment

textreview-comment.txt
mesraipilot · Bot · reviewed 2 min ago

[mesrai] [code-review] [Security] [CWE-943] [critical]

`$where` evaluates a string as JavaScript on the MongoDB server. With
`q` from `req.query.q`, you have arbitrary server-side code execution
from untrusted input.

This is not theoretical — a single request `?q=x'; while(true){}; var y='x`
will hang the database worker. The MongoDB docs have warned about this
pattern since 2014.

Replace `$where` with a structured query. The query you actually want
here is an exact-match on `username`:

  const users = await db.users.find({ username: q });

If you need partial matching, use a regex (escaped) or a text index:

  const users = await db.users.find({
    username: { $regex: escapeRegex(q), $options: "i" }
  });

Reference: https://www.mongodb.com/docs/manual/reference/operator/query/where/#security

The fix

tsapi/search-users.ts (fixed)
// api/search-users.ts — fixed
const users = await db.users.find({ username: q });

// or, for partial matching with a safe regex:
function escapeRegex(s: string) {
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
const users = await db.users.find({
  username: { $regex: escapeRegex(q), $options: "i" }
});

The structured form lets MongoDB's query engine handle the lookup — no JavaScript evaluation, no injection vector, and (incidentally) it can use the `username` index, which the `$where` form could not.

Why human review missed it

Two reasons. The `$where` operator looks ordinary if you have not seen the warning before — it reads like any other Mongo query operator. And the team's lint config did not include a rule for it because the rest of the codebase used the Mongoose ODM, which encourages structured queries by default. This file was a one-off raw query, and there was no automated check to catch it.

Related rules + further reading

Mesrai rule pack: security/no-mongo-where — flags any use of $where with interpolated input.

MongoDB docs: Server-side JavaScript security — official warning, recommends disabling JS execution entirely with --noscripting where possible.

CWE-943 — Improper Neutralization of Special Elements in Data Query Logic. Parent class for NoSQL + SQL + LDAP + others.

Takeaway

`$where` with user input is a textbook injection vector. Mesrai catches the pattern in two minutes. If you have any Mongo queries with `$where` in your codebase right now, audit them today — there is no safe way to use that operator with user-controlled strings.

// try

See it on your next PR.

Free for individuals. Install in two minutes. Mesrai reviews every commit.