Use of Password System for Primary Authentication

Description

Use of Password System for Primary Authentication is a vulnerability that occurs when password-based authentication serves as the sole or primary means of authentication. While passwords are the most common authentication mechanism due to their simplicity, they are subject to numerous well-known attacks and weaknesses that reduce their effectiveness. Dictionary attacks, brute force attempts, credential stuffing, phishing, keylogging, and social engineering all target password systems. The inherent limitations of human memory lead to weak password choices, password reuse across services, and insecure password storage practices by users.

Risk

Password-only authentication systems face persistent and evolving threats that have proven highly effective at scale. Credential stuffing attacks leverage billions of leaked username/password pairs to compromise accounts across services. Phishing campaigns successfully harvest credentials from users regardless of password complexity. Rainbow table and dictionary attacks can crack weak passwords rapidly, while GPU-accelerated brute force makes even moderately complex passwords vulnerable. The human factors are equally problematic - users create predictable passwords, reuse them extensively, and are susceptible to social engineering. Password databases, when breached, expose credentials that may remain valid across many services for years. These combined risks make password-only systems fundamentally unsuitable for protecting sensitive resources.

Solution

Implement robust password security controls combined with additional authentication mechanisms. Store passwords using modern adaptive hashing algorithms (bcrypt, argon2, scrypt) with unique salts per password. Enforce intelligent password policies that encourage length over complexity and check against known compromised password lists. Implement password aging with reasonable intervals to limit credential validity windows. Consider zero-knowledge password protocols like SRP that never expose passwords over the network. Deploy multi-factor authentication to add independent verification factors beyond passwords. Educate users about password security, phishing recognition, and the importance of unique passwords. Implement account lockout and monitoring to detect and respond to password attacks. Consider passwordless authentication alternatives using hardware tokens, biometrics, or cryptographic keys.

Common Consequences

ImpactDetails
Access ControlScope: Access Control

Attackers who compromise passwords are authorized as valid users, gaining full access to accounts and their associated privileges without any additional verification requirements.
Integrity, ConfidentialityScope: Integrity, Confidentiality

With account access, attackers can read sensitive data, perform unauthorized transactions, modify account settings, and potentially use the compromised account to attack other systems or users.

Example Code

Vulnerable Code (C)

The following examples demonstrate weak password authentication systems:

// Vulnerable: Basic password authentication with multiple weaknesses
#include <stdio.h>
#include <string.h>
#include <openssl/sha.h>

typedef struct {
    char username[64];
    char password_hash[65];  // SHA-256 hex
    // No salt stored!
} UserRecord;

// Vulnerable: Weak hash storage without salt
int vulnerable_store_password(const char *username, const char *password) {
    UserRecord record;
    strncpy(record.username, username, sizeof(record.username) - 1);

    // Vulnerable: SHA-256 without salt - rainbow table vulnerable
    unsigned char hash[SHA256_DIGEST_LENGTH];
    SHA256((unsigned char*)password, strlen(password), hash);

    for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
        sprintf(&record.password_hash[i*2], "%02x", hash[i]);
    }

    return save_user_record(&record);
}

// Vulnerable: No protection against brute force
int vulnerable_verify_password(const char *username, const char *password) {
    UserRecord *record = lookup_user(username);
    if (record == NULL) return 0;

    // Vulnerable: Same weak hash for comparison
    unsigned char hash[SHA256_DIGEST_LENGTH];
    SHA256((unsigned char*)password, strlen(password), hash);

    char input_hash[65];
    for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
        sprintf(&input_hash[i*2], "%02x", hash[i]);
    }

    // Vulnerable: Direct string comparison (timing attack)
    return strcmp(input_hash, record->password_hash) == 0;
}

// Vulnerable: No password policy enforcement
int vulnerable_set_password(const char *username, const char *new_password) {
    // Accepts any password including:
    // - Empty passwords
    // - Single character passwords
    // - Common passwords like "password123"
    // - Previously breached passwords

    return vulnerable_store_password(username, new_password);
}
# Vulnerable: Python password system with common weaknesses
import hashlib
from flask import Flask, request, session

app = Flask(__name__)

# Vulnerable: In-memory user store with weak hashing
users = {
    'admin': hashlib.md5('admin123'.encode()).hexdigest(),  # MD5 is broken!
}

@app.route('/login', methods=['POST'])
def login():
    username = request.form.get('username')
    password = request.form.get('password')

    # Vulnerable: MD5 hash without salt
    password_hash = hashlib.md5(password.encode()).hexdigest()

    # Vulnerable: Reveals whether username exists
    if username not in users:
        return "User not found", 401

    if users[username] == password_hash:
        session['user'] = username
        return "Login successful"

    return "Invalid password", 401

@app.route('/register', methods=['POST'])
def register():
    username = request.form.get('username')
    password = request.form.get('password')

    # Vulnerable: No password strength requirements
    # Accepts "a", "123", "password", etc.

    # Vulnerable: MD5 storage
    users[username] = hashlib.md5(password.encode()).hexdigest()

    return "User created"

@app.route('/change-password', methods=['POST'])
def change_password():
    new_password = request.form.get('new_password')

    # Vulnerable: No old password verification
    # Vulnerable: No password history check
    # Vulnerable: No minimum age requirement

    username = session.get('user')
    if username:
        users[username] = hashlib.md5(new_password.encode()).hexdigest()
        return "Password changed"

    return "Not logged in", 401
// Vulnerable: Java password authentication with weaknesses
import java.security.MessageDigest;
import java.util.HashMap;

public class VulnerablePasswordAuth {

    // Vulnerable: Static password store
    private static HashMap<String, String> users = new HashMap<>();

    // Vulnerable: SHA-1 is deprecated
    public String hashPassword(String password) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            byte[] hash = md.digest(password.getBytes("UTF-8"));
            StringBuilder hexString = new StringBuilder();
            for (byte b : hash) {
                hexString.append(String.format("%02x", b));
            }
            return hexString.toString();
        } catch (Exception e) {
            return null;
        }
    }

    // Vulnerable: No password policy
    public boolean registerUser(String username, String password) {
        // Accepts any password
        String hash = hashPassword(password);
        users.put(username, hash);
        return true;
    }

    // Vulnerable: Multiple issues
    public boolean authenticate(String username, String password) {
        String storedHash = users.get(username);

        // Vulnerable: Username enumeration
        if (storedHash == null) {
            return false;  // Immediate return reveals username doesn't exist
        }

        String inputHash = hashPassword(password);

        // Vulnerable: Timing attack via string comparison
        return storedHash.equals(inputHash);
    }
}

Fixed Code (C)

// Fixed: Secure password authentication system
#include <stdio.h>
#include <string.h>
#include <sodium.h>  // Using libsodium for cryptography

typedef struct {
    char username[64];
    char password_hash[crypto_pwhash_STRBYTES];  // Argon2id hash
    time_t password_created;
    time_t password_expires;
    int failed_attempts;
} UserRecord;

// Fixed: Secure password hashing with Argon2id
int secure_store_password(const char *username, const char *password) {
    // Fixed: Validate password strength first
    if (!is_password_strong(password)) {
        return -1;  // Password doesn't meet requirements
    }

    // Fixed: Check against known compromised passwords
    if (is_password_compromised(password)) {
        return -2;  // Password found in breach database
    }

    UserRecord record = {0};  // Zero-initialize
    strncpy(record.username, username, sizeof(record.username) - 1);

    // Fixed: Use Argon2id with secure parameters
    // Salt is automatically generated and embedded in hash
    if (crypto_pwhash_str(
            record.password_hash,
            password,
            strlen(password),
            crypto_pwhash_OPSLIMIT_MODERATE,
            crypto_pwhash_MEMLIMIT_MODERATE) != 0) {
        return -3;  // Out of memory
    }

    record.password_created = time(NULL);
    record.password_expires = time(NULL) + (90 * 24 * 60 * 60);  // 90 days
    record.failed_attempts = 0;

    return save_user_record(&record);
}

// Fixed: Secure verification with protections
int secure_verify_password(const char *username, const char *password) {
    UserRecord *record = lookup_user(username);

    // Fixed: Constant-time even for non-existent users
    if (record == NULL) {
        // Perform dummy verification to maintain timing
        char dummy_hash[crypto_pwhash_STRBYTES];
        crypto_pwhash_str(dummy_hash, password, strlen(password),
                         crypto_pwhash_OPSLIMIT_MODERATE,
                         crypto_pwhash_MEMLIMIT_MODERATE);
        return 0;
    }

    // Fixed: Check for account lockout
    if (record->failed_attempts >= MAX_FAILED_ATTEMPTS) {
        if (time(NULL) - record->last_attempt < LOCKOUT_DURATION) {
            return -1;  // Account locked
        }
        record->failed_attempts = 0;  // Reset after lockout period
    }

    // Fixed: Check password expiration
    if (time(NULL) > record->password_expires) {
        return -2;  // Password expired
    }

    // Fixed: Argon2id verification (constant-time internally)
    if (crypto_pwhash_str_verify(record->password_hash, password,
                                  strlen(password)) == 0) {
        record->failed_attempts = 0;
        save_user_record(record);
        return 1;  // Success
    }

    // Fixed: Track failed attempts
    record->failed_attempts++;
    record->last_attempt = time(NULL);
    save_user_record(record);

    return 0;
}

// Fixed: Password policy enforcement
int is_password_strong(const char *password) {
    size_t len = strlen(password);

    // Minimum length requirement
    if (len < 12) return 0;

    // Check character diversity (not just complexity rules)
    int has_upper = 0, has_lower = 0, has_digit = 0, has_special = 0;
    for (size_t i = 0; i < len; i++) {
        if (isupper(password[i])) has_upper = 1;
        else if (islower(password[i])) has_lower = 1;
        else if (isdigit(password[i])) has_digit = 1;
        else has_special = 1;
    }

    // Require at least 3 character classes
    int classes = has_upper + has_lower + has_digit + has_special;
    if (classes < 3) return 0;

    // Check for common patterns
    if (contains_common_pattern(password)) return 0;

    return 1;
}
# Fixed: Secure password authentication system
from flask import Flask, request, session
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
import secrets
import time
from zxcvbn import zxcvbn  # Password strength checker

app = Flask(__name__)
ph = PasswordHasher()

# Fixed: Proper user storage (use database in production)
users = {}
password_history = {}  # Track previous passwords

@app.route('/login', methods=['POST'])
def login():
    username = request.form.get('username')
    password = request.form.get('password')

    # Fixed: Rate limiting
    if is_rate_limited(username, request.remote_addr):
        return "Too many attempts, please wait", 429

    # Fixed: Constant-time behavior
    user = users.get(username)

    if user is None:
        # Perform dummy hash to maintain timing
        try:
            ph.verify(get_dummy_hash(), password)
        except:
            pass
        record_failed_attempt(username, request.remote_addr)
        return "Invalid credentials", 401

    # Fixed: Check account lockout
    if user.get('locked_until', 0) > time.time():
        return "Account locked, try again later", 423

    try:
        # Fixed: Argon2id verification
        ph.verify(user['password_hash'], password)

        # Fixed: Check if password needs rehash (parameters changed)
        if ph.check_needs_rehash(user['password_hash']):
            user['password_hash'] = ph.hash(password)

        # Reset failed attempts
        user['failed_attempts'] = 0

        # Fixed: Check password expiration
        if time.time() > user.get('password_expires', 0):
            session['must_change_password'] = True

        session['user'] = username
        session['auth_time'] = time.time()

        return "Login successful"

    except VerifyMismatchError:
        record_failed_attempt(username, request.remote_addr)
        user['failed_attempts'] = user.get('failed_attempts', 0) + 1

        if user['failed_attempts'] >= 5:
            user['locked_until'] = time.time() + 900  # 15 min lockout

        return "Invalid credentials", 401

@app.route('/register', methods=['POST'])
def register():
    username = request.form.get('username')
    password = request.form.get('password')

    # Fixed: Password strength validation
    strength = zxcvbn(password, user_inputs=[username])
    if strength['score'] < 3:
        return f"Password too weak: {strength['feedback']['warning']}", 400

    # Fixed: Check against compromised passwords
    if is_password_pwned(password):
        return "This password has been exposed in a data breach", 400

    # Fixed: Minimum length
    if len(password) < 12:
        return "Password must be at least 12 characters", 400

    # Fixed: Argon2id hashing
    password_hash = ph.hash(password)

    users[username] = {
        'password_hash': password_hash,
        'created': time.time(),
        'password_expires': time.time() + (90 * 24 * 3600),  # 90 days
        'failed_attempts': 0
    }

    password_history[username] = [password_hash]

    return "User created"

@app.route('/change-password', methods=['POST'])
def change_password():
    if 'user' not in session:
        return "Not logged in", 401

    username = session['user']
    old_password = request.form.get('old_password')
    new_password = request.form.get('new_password')

    user = users.get(username)

    # Fixed: Verify old password
    try:
        ph.verify(user['password_hash'], old_password)
    except VerifyMismatchError:
        return "Current password incorrect", 401

    # Fixed: Check password isn't reused
    history = password_history.get(username, [])
    for old_hash in history[-5:]:  # Check last 5 passwords
        try:
            ph.verify(old_hash, new_password)
            return "Cannot reuse recent passwords", 400
        except VerifyMismatchError:
            pass

    # Fixed: Validate new password strength
    strength = zxcvbn(new_password, user_inputs=[username])
    if strength['score'] < 3:
        return f"Password too weak: {strength['feedback']['warning']}", 400

    # Fixed: Update password
    new_hash = ph.hash(new_password)
    user['password_hash'] = new_hash
    user['password_expires'] = time.time() + (90 * 24 * 3600)

    # Track in history
    history.append(new_hash)
    password_history[username] = history[-10:]  # Keep last 10

    session.pop('must_change_password', None)

    return "Password changed successfully"

The fix implements secure password hashing, strength validation, breach checking, and expiration policies.


Exploited in the Wild

Credential Stuffing Campaigns (Ongoing)

Massive credential stuffing attacks using leaked credentials from breaches have compromised millions of accounts protected only by passwords across financial services, e-commerce, and social media platforms.

Password Spray Attacks (Enterprise, Ongoing)

Password spray attacks testing common passwords against many accounts have successfully compromised enterprise environments, particularly those without lockout policies or MFA.


Tools to Test/Exploit

  • Hydra — Password cracking tool supporting numerous protocols.

  • Hashcat — Advanced password recovery tool for testing hash strength.

  • Have I Been Pwned API — Service to check passwords against known breaches.


CVE Examples

Password system weaknesses typically contribute to broader authentication failures rather than receiving standalone CVEs. Related examples include:

  • CVE-2019-11358 — Weak password hashing in web application.

  • CVE-2020-1472 — Zerologon - authentication bypass due to cryptographic weakness.


References

  1. MITRE Corporation. "CWE-309: Use of Password System for Primary Authentication." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/309.html

  2. NIST. "Digital Identity Guidelines." SP 800-63B. https://pages.nist.gov/800-63-3/sp800-63b.html

  3. OWASP Foundation. "Password Storage Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html