auth_lab.py — Source Code

Back Download
/labs/auth_lab.py
#!/usr/bin/env python3
"""
Authentication Attack Lab
The Hacker's Arsenal - Lesson 6

Practice authentication bypass techniques:
1. Basic login (no protection) - brute force target
2. Login with account lockout
3. Login with rate limiting (bypassable via X-Forwarded-For)
4. Login with weak CAPTCHA
5. Username enumeration

Run this script and open http://localhost:8888 in your browser.

WARNING: This code is INTENTIONALLY VULNERABLE for educational purposes.
"""

from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import urlparse, parse_qs
import time

# Simulated user database
USERS = {
    'admin': {'password': 'admin123', 'locked': False, 'attempts': 0},
    'john': {'password': 'password1', 'locked': False, 'attempts': 0},
    'jane': {'password': 'qwerty', 'locked': False, 'attempts': 0},
    'bob': {'password': 'letmein', 'locked': False, 'attempts': 0},
    'alice': {'password': 'Summer2024!', 'locked': False, 'attempts': 0},
}

# Rate limiting storage
REQUEST_LOG = {}
LOCKOUT_THRESHOLD = 5
LOCKOUT_DURATION = 30

HTML_TEMPLATE = '''<!DOCTYPE html>
<html>
<head>
    <title>Auth Lab</title>
    <style>
        body {{ font-family: Arial; max-width: 800px; margin: 50px auto; background: #1a1a1a; color: #e0e0e0; }}
        h1 {{ color: #6EFF24; }}
        h2 {{ color: #6EFF24; font-size: 1.3em; }}
        .section {{ background: #252525; padding: 20px; margin: 20px 0; border-radius: 8px; border-left: 3px solid #6EFF24; }}
        input {{ padding: 10px; margin: 5px; background: #333; border: 1px solid #6EFF24; color: white; width: 200px; }}
        button {{ background: #6EFF24; color: black; padding: 10px 20px; border: none; cursor: pointer; font-weight: bold; }}
        .error {{ color: #ff4444; background: #331111; padding: 10px; border-radius: 5px; margin: 10px 0; }}
        .success {{ color: #44ff44; background: #113311; padding: 10px; border-radius: 5px; margin: 10px 0; }}
        .warning {{ color: #ffaa44; background: #332211; padding: 10px; border-radius: 5px; margin: 10px 0; }}
        .hint {{ color: #888; font-size: 0.9em; margin-top: 10px; }}
        code {{ background: #333; padding: 2px 6px; color: #6EFF24; }}
        .info {{ background: #1a2a3a; padding: 10px; border-radius: 5px; margin: 10px 0; }}
    </style>
</head>
<body>
    <h1>Authentication Attack Lab</h1>

    <div class="section">
        <h2>1. Basic Login (No Protection)</h2>
        <form action="/login1" method="POST">
            <input type="text" name="username" placeholder="Username"><br>
            <input type="password" name="password" placeholder="Password"><br>
            <button type="submit">Login</button>
        </form>
        {login1_result}
        <p class="hint">Hint: Common usernames include admin, john, jane, bob, alice</p>
    </div>

    <div class="section">
        <h2>2. Login with Lockout (5 attempts)</h2>
        <form action="/login2" method="POST">
            <input type="text" name="username" placeholder="Username"><br>
            <input type="password" name="password" placeholder="Password"><br>
            <button type="submit">Login</button>
        </form>
        {login2_result}
        <p class="hint">Hint: How can you test passwords without triggering lockout?</p>
    </div>

    <div class="section">
        <h2>3. Login with Rate Limiting</h2>
        <form action="/login3" method="POST">
            <input type="text" name="username" placeholder="Username"><br>
            <input type="password" name="password" placeholder="Password"><br>
            <button type="submit">Login</button>
        </form>
        {login3_result}
        <p class="hint">Hint: Rate limiting is by IP. What if you could change your IP?</p>
    </div>

    <div class="section">
        <h2>4. Login with CAPTCHA</h2>
        <form action="/login4" method="POST">
            <input type="text" name="username" placeholder="Username"><br>
            <input type="password" name="password" placeholder="Password"><br>
            <input type="text" name="captcha" placeholder="What is 7 + 3?"><br>
            <button type="submit">Login</button>
        </form>
        {login4_result}
        <p class="hint">Hint: Is this CAPTCHA actually secure?</p>
    </div>

    <div class="section">
        <h2>5. Username Enumeration</h2>
        <form action="/check_user" method="GET">
            <input type="text" name="username" placeholder="Check username"><br>
            <button type="submit">Check Availability</button>
        </form>
        {enum_result}
        <p class="hint">Hint: Watch for differences in responses between valid and invalid users</p>
    </div>

    <div class="section" style="border-left-color: #ff4444;">
        <h2>Challenge Mode</h2>
        <ol>
            <li>Brute force the admin password using Burp Intruder</li>
            <li>Bypass the account lockout to attack john's account</li>
            <li>Enumerate all valid usernames</li>
            <li>Find a way past the rate limiter</li>
        </ol>
        <div class="info">
            <strong>Users:</strong> admin, john, jane, bob, alice<br>
            <strong>Password hints:</strong> Common passwords, seasonal patterns
        </div>
    </div>
</body>
</html>'''


class AuthLabHandler(BaseHTTPRequestHandler):
    def get_client_ip(self):
        # VULNERABLE: Trusts X-Forwarded-For header
        xff = self.headers.get('X-Forwarded-For')
        if xff:
            return xff.split(',')[0].strip()
        return self.client_address[0]

    def do_GET(self):
        parsed = urlparse(self.path)
        params = parse_qs(parsed.query)

        enum_result = ''

        if parsed.path == '/check_user':
            username = params.get('username', [''])[0]
            # VULNERABLE: Different responses reveal valid usernames
            if username in USERS:
                enum_result = '<p class="warning">Username is taken</p>'
            else:
                enum_result = '<p class="info">Username is available</p>'

        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.end_headers()

        page = HTML_TEMPLATE.format(
            login1_result='',
            login2_result='',
            login3_result='',
            login4_result='',
            enum_result=enum_result
        )
        self.wfile.write(page.encode())

    def do_POST(self):
        parsed = urlparse(self.path)
        content_length = int(self.headers['Content-Length'])
        post_data = self.rfile.read(content_length).decode()
        params = parse_qs(post_data)

        username = params.get('username', [''])[0]
        password = params.get('password', [''])[0]

        login1_result = ''
        login2_result = ''
        login3_result = ''
        login4_result = ''

        if parsed.path == '/login1':
            # No protection - vulnerable to brute force
            if username in USERS and USERS[username]['password'] == password:
                login1_result = f'<p class="success">Welcome, {username}!</p>'
            else:
                login1_result = '<p class="error">Invalid credentials</p>'

        elif parsed.path == '/login2':
            # Account lockout after 5 attempts
            if username in USERS:
                user = USERS[username]
                if user['locked']:
                    login2_result = '<p class="error">Account locked. Try again later.</p>'
                elif user['password'] == password:
                    user['attempts'] = 0
                    login2_result = f'<p class="success">Welcome, {username}!</p>'
                else:
                    user['attempts'] += 1
                    remaining = LOCKOUT_THRESHOLD - user['attempts']
                    if user['attempts'] >= LOCKOUT_THRESHOLD:
                        user['locked'] = True
                        login2_result = '<p class="error">Account locked after too many attempts!</p>'
                    else:
                        login2_result = f'<p class="error">Invalid password. {remaining} attempts remaining.</p>'
            else:
                # VULNERABLE: Different message for invalid username
                login2_result = '<p class="error">User does not exist</p>'

        elif parsed.path == '/login3':
            # Rate limiting by IP (bypassable)
            client_ip = self.get_client_ip()
            current_time = time.time()

            REQUEST_LOG[client_ip] = [t for t in REQUEST_LOG.get(client_ip, [])
                                       if current_time - t < LOCKOUT_DURATION]

            if len(REQUEST_LOG.get(client_ip, [])) >= LOCKOUT_THRESHOLD:
                login3_result = '<p class="error">Too many requests. Please wait.</p>'
            else:
                REQUEST_LOG.setdefault(client_ip, []).append(current_time)

                if username in USERS and USERS[username]['password'] == password:
                    login3_result = f'<p class="success">Welcome, {username}!</p>'
                else:
                    login3_result = '<p class="error">Invalid credentials</p>'

        elif parsed.path == '/login4':
            # VULNERABLE: Static CAPTCHA
            captcha = params.get('captcha', [''])[0]

            if captcha != '10':
                login4_result = '<p class="error">Invalid CAPTCHA</p>'
            elif username in USERS and USERS[username]['password'] == password:
                login4_result = f'<p class="success">Welcome, {username}!</p>'
            else:
                login4_result = '<p class="error">Invalid credentials</p>'

        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.end_headers()

        page = HTML_TEMPLATE.format(
            login1_result=login1_result,
            login2_result=login2_result,
            login3_result=login3_result,
            login4_result=login4_result,
            enum_result=''
        )
        self.wfile.write(page.encode())

    def log_message(self, format, *args):
        print(f"[{self.get_client_ip()}] {args[0]}")


if __name__ == '__main__':
    server = HTTPServer(('localhost', 8888), AuthLabHandler)
    print("=" * 50)
    print("  AUTHENTICATION LAB RUNNING")
    print("  Open http://localhost:8888 in your browser")
    print("=" * 50)
    server.serve_forever()