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
| Impact | Details |
|---|---|
| Access Control | Scope: 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, Confidentiality | Scope: 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
-
MITRE Corporation. "CWE-309: Use of Password System for Primary Authentication." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/309.html
-
NIST. "Digital Identity Guidelines." SP 800-63B. https://pages.nist.gov/800-63-3/sp800-63b.html
-
OWASP Foundation. "Password Storage Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html