Weakness reference
CWE-309

Use of Password System for Primary Authentication

This weakness occurs when an application relies solely on passwords to verify user identity, without additional verification methods. Passwords alone are…

01Summary

This weakness occurs when an application relies solely on passwords to verify user identity, without additional verification methods. Passwords alone are vulnerable to guessing, brute force, credential theft, and phishing — making them an insufficient foundation for protecting sensitive accounts or data. Modern security practice treats passwords as one factor among several, not as a complete solution.

02How It Happens

Password-only authentication creates a single point of failure: if an attacker obtains or guesses a user's password, they gain full access to that account. This happens because passwords are inherently weak compared to multi-factor schemes — they can be reused across sites, forgotten and reset via predictable recovery flows, intercepted during transmission, or compromised in third-party breaches. The software design assumes that password strength and user discipline are sufficient safeguards, but in practice, users choose weak passwords, reuse them, and fall victim to phishing. No amount of password policy enforcement can fully compensate for the absence of a second verification factor.

03Real-World Impact

Attackers who obtain a user's password — through phishing, credential stuffing, malware, or data breaches — gain immediate, unrestricted access to that account. For administrative accounts, this can lead to full system compromise, data exfiltration, or malware installation. For customer accounts, it enables fraud, identity theft, or unauthorized transactions. Even if the password is strong, a single compromised credential can unlock sensitive operations without any additional verification step to slow or stop the attacker.

04Vulnerable & Fixed Patterns

Vulnerable pattern
import sqlite3

def authenticate_user(username, password):
    conn = sqlite3.connect('users.db')
    cursor = conn.cursor()
    cursor.execute(
        'SELECT id FROM users WHERE username = ? AND password = ?',
        (username, password)
    )
    user = cursor.fetchone()
    conn.close()
    
    if user:
        return True  # User authenticated; grant access
    return False

Why it's vulnerable:
The function grants full access based solely on username and password match. No second factor (SMS code, authenticator app, security question, etc.) is required, so a stolen password is sufficient for account takeover.

Fixed pattern
import sqlite3
import pyotp

def authenticate_user(username, password, totp_code):
    conn = sqlite3.connect('users.db')
    cursor = conn.cursor()
    cursor.execute(
        'SELECT id, totp_secret FROM users WHERE username = ? AND password = ?',
        (username, password)
    )
    user = cursor.fetchone()
    conn.close()
    
    if not user:
        return False
    
    # Verify second factor (time-based one-time password)
    totp = pyotp.TOTP(user[1])
    if totp.verify(totp_code):
        return True
    return False
Vulnerable pattern
<?php
$username = $_POST['username'];
$password = $_POST['password'];

$mysqli = new mysqli('localhost', 'app_user', 'app_pass', 'app_db');
$result = $mysqli->query(
    "SELECT id FROM users WHERE username = '$username' AND password = '$password'"
);

if ($result->num_rows > 0) {
    $_SESSION['user_id'] = $result->fetch_assoc()['id'];
    echo "Login successful";
} else {
    echo "Invalid credentials";
}
?>

Why it's vulnerable:
The script authenticates the user based only on password match and immediately grants session access. No second verification step exists, so password compromise equals account compromise.

Fixed pattern
<?php
$username = $_POST['username'];
$password = $_POST['password'];
$totp_code = $_POST['totp_code'];

$mysqli = new mysqli('localhost', 'app_user', 'app_pass', 'app_db');
$stmt = $mysqli->prepare('SELECT id, totp_secret FROM users WHERE username = ?');
$stmt->bind_param('s', $username);
$stmt->execute();
$result = $stmt->get_result();

if ($result->num_rows > 0) {
    $user = $result->fetch_assoc();
    // Verify password (should use password_verify with hashed password)
    if (password_verify($password, $user['password_hash'])) {
        // Verify second factor
        require 'vendor/autoload.php';
        $totp = new \OTPHP\TOTP($user['totp_secret']);
        if ($totp->verify($totp_code)) {
            $_SESSION['user_id'] = $user['id'];
            echo "Login successful";
        } else {
            echo "Invalid authentication code";
        }
    }
}
?>

05Prevention Checklist

Implement multi-factor authentication (MFA)
for all user accounts, especially administrative and privileged roles. Support at least one of: time-based one-time passwords (TOTP), SMS/email codes, or hardware security keys.
Require MFA for sensitive operations
— even if login uses only a password, enforce a second factor for password changes, permission grants, or data exports.
Use strong password hashing
(bcrypt, Argon2, scrypt) and enforce reasonable length/complexity policies, but recognize these do not replace MFA.
Implement account lockout and rate limiting
on login attempts to slow brute-force attacks, and log failed attempts for monitoring.
Offer passwordless alternatives
where feasible — magic links, biometric authentication, or hardware keys — to reduce reliance on password strength.
Educate users
about phishing and credential reuse; provide clear guidance on enabling MFA during onboarding.

06Signs You May Already Be Affected

Check your authentication logs for unusual login patterns: multiple failed attempts from different IP addresses, successful logins from unexpected geographic locations, or logins at unusual times. Review your user account creation and password reset flows — if users can reset a password with only email verification (no second factor), attackers who compromise email can take over accounts. Audit your administrative and high-privilege accounts to confirm MFA is enabled; if any admin account relies on password-only authentication, that is a critical gap.