Vulnerability class

NoSQL injection in Python

NoSQL injection is a flaw where data an attacker controls changes the structure of a database query rather than supplying a value to it, most often by smuggling query operators into a document store like MongoDB.

CWE
CWE-943
OWASP
A05:2025 Injection
Tag
nosql injection
On this page

How it shows up in Python

MongoDB does not parse a string query, so classic SQL-style injection does not apply, but a different door is open. A filter is a dictionary, and if a request value is dropped into it unchanged, the attacker can replace a plain value with an operator document:

from flask import request

def find_user(db, body):
    return db.users.find_one({"username": body["username"], "password": body["password"]})

If the request body is JSON and the client sends {"$ne": null} where a password string was expected, the filter becomes "password not equal to null", which matches a record regardless of the real password. The fix is to force each value to the type you expect before it reaches the query, so an operator document can never take a value's place:

from flask import request, abort

def find_user(db, body):
    username = body.get("username")
    password = body.get("password")
    if not isinstance(username, str) or not isinstance(password, str):
        abort(400)
    return db.users.find_one({"username": username, "password": password})

A schema library such as Pydantic does the same at the edge and gives you typed fields everywhere after. Two deeper cases need care: a $where clause or a mapReduce runs a JavaScript expression inside the database, so a tainted value there is code injection, not just operator injection, so keep user input out of it; and Redis is safe for keys and values (its protocol frames them by length) but EVAL runs a Lua script, so a script body built from user input is injection into that script.

Why it is hard to see

  • The payload is structure, not characters. {"$ne": null} contains no SQL metacharacters and no shell syntax, so signature rules written for those find nothing to flag.
  • It arrives as ordinary JSON. The attack is a well-formed JSON object; a web application firewall sees a valid body.
  • The operator is added in the application. The request sends a value and the code wraps it in a filter dict, so the dangerous shape only exists at the query call.
  • No request at all. A worker builds a MongoDB filter from a queue message or a stored document.

What the es-python event looks like

es-python reports when untrusted data reaches a query call on a supported driver. For PyMongo it covers the Collection methods that take a filter or a pipeline (find, find_one, count_documents, the delete_*, update_* and find_one_and_* methods, and aggregate), with the label nosql mongodb. For Redis it covers eval and eval_ro, the Lua entry points, with the label nosql redis. Both carry the nosql injection impact tag. For MongoDB the report narrows to the parts that matter: the $-prefixed operator keys in the filter, and the value of the code-bearing operators such as $where, $expr and $function.

Redis is in the list only for EVAL, and that is deliberate: a tainted key or value cannot escape its length-prefixed RESP field, so storing user data in Redis is the normal case and does not fire. A tainted Lua script body is genuine code injection into the database, and that is what the nosql redis label marks. With network data the event is graded high. Under a Raise rule, the query raises a RuntimeError before it runs.

How to fix and prevent it

  1. Coerce every request value to its expected type before it reaches a query, so an operator document cannot stand in for a value. Validate with a schema library at the edge.
  2. Keep user input out of every $where, $expr, $function or mapReduce body and Redis Lua script.
  3. Keep operator keys out of user-controlled positions: a filter's keys should come from your code, not from the request.
  4. Give the database account the least privilege it needs.
  5. In Report mode, watch the nosql injection tag to see which queries receive request data directly.

Classification

NoSQL injection is CWE-943, Improper Neutralization of Special Elements in Data Query Logic, a child of CWE-74, Injection. CWE-943 is not individually listed in the OWASP 2025 mapping, but its parent CWE-74 sits under A05:2025 Injection, which is where this class belongs.

Questions people ask

The questions that come up on forums and in reviews about this class, each answered in a few sentences.

Is MongoDB vulnerable to injection from Python?

Yes, in a different shape from SQL: the risk is not a string that becomes query text but a value that becomes a query operator, such as a request parameter that arrives as a dictionary with a $ne key instead of a string and matches every document. PyMongo passes a dictionary through as written, so the fix is to check the type of each value before it goes into a filter and to reject keys that start with $. $where and the other code-bearing operators take JavaScript and deserve the same care as eval().

How do I prevent NoSQL injection in PyMongo?

Validate the type of every value that reaches a filter, so a string field cannot arrive as a dictionary of operators, and build filters from fields you name rather than from a request body passed through whole. Avoid $where, $expr with user input and $function, which evaluate code on the server. es-python reports the $-prefixed operator keys and the code-bearing operator values that came from untrusted data, so the shapes that matter are the ones you see.

How does es-python report NoSQL injection?

It reports when untrusted data reaches a PyMongo query method, find, update_one, aggregate and the rest, or a Redis EVAL, with the label nosql mongodb or nosql redis and the nosql injection impact tag. For MongoDB the report narrows to the operator keys and the code-bearing operator values, because a tainted plain value is the normal case. With network data the event is graded High, and a Raise rule stops the query before it runs.

Watch for NoSQL injection in your own code

es-python runs your Python application unchanged and reports when untrusted data reaches the operation, with where the data came from and the line that used it. Book a live demo to see it on a real application.

EyalSec Pricing Docs Security Research Vulnerabilities Security glossary About Contact עברית Login Book a live demo