Improper Authentication

Description

Improper Authentication occurs when an actor claims to have a given identity but the software does not prove or insufficiently proves that the claim is correct. This encompasses a wide range of authentication flaws including weak password mechanisms, missing multi-factor authentication, predictable tokens, bypassable authentication logic, and flawed session management. When authentication can be bypassed or circumvented, attackers gain unauthorized access to protected resources, user accounts, or administrative functions.

Risk

Improper authentication is consistently ranked among the most critical security weaknesses. Successful exploitation enables attackers to access user accounts, administrative panels, and restricted functionality. The impact ranges from unauthorized data access to complete system compromise. Attack vectors include brute-force attacks, credential stuffing, session hijacking, Man-in-the-Middle attacks, and authentication bypass. The 2017 Equifax breach and countless other major incidents stemmed from authentication weaknesses. Weak OTP mechanisms without rate limiting, as seen in recent WordPress plugin vulnerabilities, allow brute-force attacks that completely bypass authentication.

Solution

Implement strong authentication mechanisms using established frameworks and libraries. Use multi-factor authentication (MFA) for sensitive operations. Implement account lockout and rate limiting to prevent brute-force attacks. Use secure session management with cryptographically random tokens. Apply proper password hashing (bcrypt, Argon2). Implement secure password policies. Use HTTPS for all authentication traffic. Validate authentication on every request for protected resources. Consider passwordless authentication methods. Conduct regular security assessments focusing on authentication bypass.

Common Consequences

ImpactDetails
Access ControlScope: Authentication Bypass

Attackers gain unauthorized access to user accounts, administrative functions, and protected resources.
ConfidentialityScope: Data Breach

Bypassed authentication exposes all data the compromised user/role can access.
IntegrityScope: System Compromise

Administrative access through authentication bypass enables complete system control.

Example Code + Solution Code

Vulnerable Code

# VULNERABLE: No rate limiting on OTP
@app.route('/verify-otp', methods=['POST'])
def verify_otp():
    user_otp = request.form['otp']
    stored_otp = session.get('otp')

    # 6-digit OTP valid for 10 minutes
    # No rate limiting = brute-forceable (1 million attempts max)
    if user_otp == stored_otp:
        session['authenticated'] = True
        return redirect('/dashboard')
    return 'Invalid OTP', 401

# VULNERABLE: Weak authentication logic
@app.route('/admin')
def admin_panel():
    # Only checks if username exists, not password
    if 'username' in session:
        return render_template('admin.html')
    return redirect('/login')

# VULNERABLE: Predictable session tokens
def create_session(user_id):
    # Predictable token - easily guessable
    token = f"session_{user_id}_{int(time.time())}"
    return token
// VULNERABLE: SQL injection in authentication
function authenticate($username, $password) {
    $query = "SELECT * FROM users WHERE username='$username' AND password='$password'";
    // ' OR '1'='1' -- bypasses authentication
    $result = mysqli_query($conn, $query);
    return mysqli_num_rows($result) > 0;
}

// VULNERABLE: Client-side only authentication check
function checkAuth() {
    // JavaScript check easily bypassed
    $isAdmin = isset($_COOKIE['isAdmin']) && $_COOKIE['isAdmin'] === 'true';
    return $isAdmin;
}

Fixed Code

import secrets
from datetime import datetime, timedelta
from functools import wraps

# SAFE: Rate-limited OTP with lockout
class OTPManager:
    def __init__(self):
        self.attempts = {}  # user_id: (attempts, lockout_until)
        self.MAX_ATTEMPTS = 5
        self.LOCKOUT_DURATION = timedelta(minutes=15)

    def verify_otp(self, user_id, submitted_otp, stored_otp):
        # Check lockout
        if user_id in self.attempts:
            attempts, lockout_until = self.attempts[user_id]
            if lockout_until and datetime.now() < lockout_until:
                raise AuthError("Account locked. Try again later.")

        # Constant-time comparison to prevent timing attacks
        if secrets.compare_digest(submitted_otp, stored_otp):
            self.attempts.pop(user_id, None)
            return True

        # Track failed attempts
        attempts = self.attempts.get(user_id, (0, None))[0] + 1
        lockout_until = None
        if attempts >= self.MAX_ATTEMPTS:
            lockout_until = datetime.now() + self.LOCKOUT_DURATION
            attempts = 0

        self.attempts[user_id] = (attempts, lockout_until)
        return False

# SAFE: Proper authentication decorator
def require_auth(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        # Server-side session validation
        session_token = request.cookies.get('session_token')
        if not session_token:
            return redirect('/login')

        # Validate token exists and not expired
        user = validate_session_token(session_token)
        if not user:
            return redirect('/login')

        g.current_user = user
        return f(*args, **kwargs)
    return decorated

# SAFE: Cryptographically secure tokens
def create_session_safe(user_id):
    token = secrets.token_urlsafe(32)  # 256 bits of entropy
    store_session(token, user_id, expires_in=3600)
    return token
// SAFE: Parameterized authentication
function authenticate_safe($username, $password) {
    $stmt = $conn->prepare("SELECT id, password_hash FROM users WHERE username = ?");
    $stmt->bind_param("s", $username);
    $stmt->execute();
    $result = $stmt->get_result();

    if ($row = $result->fetch_assoc()) {
        // Verify against hashed password
        if (password_verify($password, $row['password_hash'])) {
            // Create secure session
            session_regenerate_id(true);
            $_SESSION['user_id'] = $row['id'];
            $_SESSION['authenticated'] = true;
            return true;
        }
    }
    return false;
}

// SAFE: Server-side authentication verification
function require_admin() {
    session_start();
    if (!isset($_SESSION['authenticated']) || !$_SESSION['authenticated']) {
        header('Location: /login');
        exit;
    }
    // Verify role from database, not cookie
    $user = get_user($_SESSION['user_id']);
    if ($user['role'] !== 'admin') {
        http_response_code(403);
        exit('Forbidden');
    }
}

Exploited in the Wild

WordPress WPCOM Member OTP Bypass (WordPress, 2025)

CVE-2025-14002 in WordPress WPCOM Member plugin allows brute-force attacks against 6-digit OTP codes due to missing rate limiting. Valid for 10 minutes with no attempt limiting, the OTP is easily brute-forceable.

Schneider Electric EPAS-UI (Industrial, 2025)

CVE-2025-0813 allows authentication bypass when unauthorized users with physical access can interrupt the normal boot process on EPAS-UI workstations.

Equifax Data Breach (Equifax, 2017)

The massive Equifax breach exposing 147 million records was enabled by a combination of authentication and authorization weaknesses in their web application framework.


Tools to test/exploit

  • Burp Suite — intercept and manipulate authentication flows.

  • Hydra — brute-force authentication testing.

  • OWASP ZAP — automated authentication testing.


CVE Examples


References

  1. MITRE. "CWE-287: Improper Authentication." https://cwe.mitre.org/data/definitions/287.html

  2. OWASP. "Authentication Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html