Weak Password Requirements
Description
Weak Password Requirements is a vulnerability where a product does not require users to have strong passwords, enabling attackers to easily guess credentials. Authentication systems depend on memorized secrets (passwords) to verify user identity, and password complexity must be sufficient to prevent attackers from guessing credentials through brute force or dictionary attacks. Weak requirements such as allowing short passwords, not requiring character variety, permitting common dictionary words, or not blocking contextual strings make it significantly easier for attackers to compromise user accounts.
Risk
Weak password requirements directly enable credential-based attacks. Short passwords can be brute-forced in seconds to hours depending on length. Passwords without complexity requirements are vulnerable to dictionary attacks using common word lists. Allowing usernames, application names, or common phrases in passwords makes accounts trivially compromised. Without password reuse restrictions, credential stuffing attacks using breached databases succeed at high rates. Weak passwords on administrative accounts can lead to complete system compromise. The cumulative effect is that attackers can gain unauthorized access to user accounts and potentially escalate to system-wide breaches.
Solution
Implement comprehensive password requirements: enforce minimum length (at least 8 characters, preferably 12+), maximum length allowing for passphrases, and block common dictionary passwords. Restrict contextual strings like usernames or application names. Consider requiring mixed character sets but prioritize length over complexity for better security and usability. Implement password strength meters to guide users. Use bcrypt, Argon2, or PBKDF2 for password hashing. Add multi-factor authentication as defense-in-depth. Consider passphrase-based approaches that encourage longer, more memorable passwords. Note that forced periodic password changes are no longer recommended as they often lead to weaker passwords.
Common Consequences
| Impact | Details |
|---|---|
| Access Control | Scope: Access Control Gain Privileges or Assume Identity - An attacker could easily guess user passwords and gain unauthorized access to accounts, potentially including administrative accounts with elevated privileges. |
| Confidentiality | Scope: Confidentiality Read Application Data - Compromised accounts may provide access to sensitive user data, personal information, or confidential business information. |
Example Code
Vulnerable Code
// Vulnerable: No password strength validation
public class VulnerableAuthService {
// Vulnerable: No minimum length requirement
public boolean registerUser(String username, String password) {
// Vulnerable: Accepts any password
if (password != null && !password.isEmpty()) {
User user = new User(username, hashPassword(password));
userRepository.save(user);
return true;
}
return false;
}
// Vulnerable: Weak password hash
private String hashPassword(String password) {
return DigestUtils.md5Hex(password); // MD5 is insecure
}
}
// Vulnerable: Minimal validation that's easily bypassed
public class WeakPasswordValidator {
// Vulnerable: Only checks length
public boolean validatePassword(String password) {
return password != null && password.length() >= 4; // Too short!
}
// Vulnerable: Simple complexity check easily satisfied
public boolean hasUpperAndLower(String password) {
return password.matches(".*[A-Z].*") &&
password.matches(".*[a-z].*");
// "Aa" would pass - not secure
}
}
# Vulnerable: Python registration with weak requirements
from flask import Flask, request
import hashlib
app = Flask(__name__)
@app.route('/register', methods=['POST'])
def vulnerable_register():
username = request.form['username']
password = request.form['password']
# Vulnerable: No length check
# Vulnerable: No complexity requirements
# Vulnerable: No common password check
# Vulnerable: Weak hash
password_hash = hashlib.md5(password.encode()).hexdigest()
save_user(username, password_hash)
return "User registered"
# Vulnerable: Accepts extremely weak passwords
def validate_password_weak(password):
# Vulnerable: Only checks for non-empty
return bool(password)
# Vulnerable: Trivially satisfiable requirements
def validate_password_minimal(password):
# Vulnerable: 6 characters is too short
if len(password) < 6:
return False
# No other checks - "aaaaaa" would pass
return True
// Vulnerable: Client-side only validation
function vulnerableValidatePassword(password) {
// Vulnerable: No server-side validation
// Attacker can bypass by calling API directly
// Vulnerable: Weak requirements
if (password.length >= 4) {
return true;
}
return false;
}
// Vulnerable: Express.js registration endpoint
app.post('/register', (req, res) => {
const { username, password } = req.body;
// Vulnerable: No password validation
// Vulnerable: Allows "1234", "password", "admin", etc.
const user = createUser(username, password);
res.json({ success: true });
});
Fixed Code
// Fixed: Comprehensive password validation
public class SecurePasswordValidator {
private static final int MIN_LENGTH = 12;
private static final int MAX_LENGTH = 128;
private static final Set<String> COMMON_PASSWORDS = loadCommonPasswords();
public ValidationResult validatePassword(String password, String username) {
List<String> errors = new ArrayList<>();
// Fixed: Check null/empty
if (password == null || password.isEmpty()) {
return ValidationResult.invalid("Password is required");
}
// Fixed: Enforce minimum length
if (password.length() < MIN_LENGTH) {
errors.add("Password must be at least " + MIN_LENGTH + " characters");
}
// Fixed: Enforce maximum length (prevent DoS on hashing)
if (password.length() > MAX_LENGTH) {
errors.add("Password must not exceed " + MAX_LENGTH + " characters");
}
// Fixed: Block common passwords
if (COMMON_PASSWORDS.contains(password.toLowerCase())) {
errors.add("This password is too common");
}
// Fixed: Block contextual strings
if (username != null && password.toLowerCase()
.contains(username.toLowerCase())) {
errors.add("Password cannot contain your username");
}
// Fixed: Check for character variety (optional, prefer length)
if (!hasCharacterVariety(password)) {
errors.add("Password should contain a mix of character types");
}
// Fixed: Check against breached passwords (optional)
if (isBreachedPassword(password)) {
errors.add("This password has appeared in data breaches");
}
return errors.isEmpty() ?
ValidationResult.valid() :
ValidationResult.invalid(errors);
}
private boolean hasCharacterVariety(String password) {
boolean hasLower = password.matches(".*[a-z].*");
boolean hasUpper = password.matches(".*[A-Z].*");
boolean hasDigit = password.matches(".*\\d.*");
boolean hasSpecial = password.matches(".*[!@#$%^&*(),.?\":{}|<>].*");
// Require at least 3 of 4 character types
int count = 0;
if (hasLower) count++;
if (hasUpper) count++;
if (hasDigit) count++;
if (hasSpecial) count++;
return count >= 3;
}
private boolean isBreachedPassword(String password) {
// Use HaveIBeenPwned k-Anonymity API
return PwnedPasswordsService.isCompromised(password);
}
private static Set<String> loadCommonPasswords() {
// Load top 10000 common passwords
return CommonPasswordLoader.load("/common-passwords.txt");
}
}
// Fixed: Secure password hashing
public class SecureAuthService {
private static final int BCRYPT_COST = 12;
public boolean registerUser(String username, String password) {
// Fixed: Validate password strength
ValidationResult result = passwordValidator
.validatePassword(password, username);
if (!result.isValid()) {
throw new WeakPasswordException(result.getErrors());
}
// Fixed: Use strong password hashing
String hash = BCrypt.hashpw(password, BCrypt.gensalt(BCRYPT_COST));
User user = new User(username, hash);
userRepository.save(user);
return true;
}
public boolean verifyPassword(String username, String password) {
User user = userRepository.findByUsername(username);
if (user == null) {
// Fixed: Prevent timing attacks
BCrypt.hashpw(password, BCrypt.gensalt(BCRYPT_COST));
return false;
}
return BCrypt.checkpw(password, user.getPasswordHash());
}
}
# Fixed: Secure password validation in Python
import re
import bcrypt
from typing import List, Tuple
import requests
class SecurePasswordValidator:
MIN_LENGTH = 12
MAX_LENGTH = 128
def __init__(self):
self.common_passwords = self._load_common_passwords()
def validate(self, password: str, username: str = None) -> Tuple[bool, List[str]]:
errors = []
if not password:
return False, ["Password is required"]
# Fixed: Enforce length requirements
if len(password) < self.MIN_LENGTH:
errors.append(f"Password must be at least {self.MIN_LENGTH} characters")
if len(password) > self.MAX_LENGTH:
errors.append(f"Password must not exceed {self.MAX_LENGTH} characters")
# Fixed: Block common passwords
if password.lower() in self.common_passwords:
errors.append("This password is too common")
# Fixed: Block username in password
if username and username.lower() in password.lower():
errors.append("Password cannot contain your username")
# Fixed: Check character variety
if not self._has_variety(password):
errors.append("Password should include mixed characters")
# Fixed: Check against breached passwords
if self._is_breached(password):
errors.append("This password has been exposed in data breaches")
return len(errors) == 0, errors
def _has_variety(self, password: str) -> bool:
checks = [
re.search(r'[a-z]', password), # lowercase
re.search(r'[A-Z]', password), # uppercase
re.search(r'\d', password), # digit
re.search(r'[!@#$%^&*(),.?":{}|<>]', password) # special
]
return sum(bool(c) for c in checks) >= 3
def _is_breached(self, password: str) -> bool:
# Use HaveIBeenPwned k-Anonymity API
import hashlib
sha1 = hashlib.sha1(password.encode()).hexdigest().upper()
prefix, suffix = sha1[:5], sha1[5:]
response = requests.get(f'https://api.pwnedpasswords.com/range/{prefix}')
if response.status_code == 200:
hashes = dict(line.split(':') for line in response.text.splitlines())
return suffix in hashes
return False
def _load_common_passwords(self) -> set:
# Load common password list
with open('common-passwords.txt', 'r') as f:
return set(line.strip().lower() for line in f)
# Fixed: Secure registration endpoint
from flask import Flask, request, jsonify
app = Flask(__name__)
password_validator = SecurePasswordValidator()
@app.route('/register', methods=['POST'])
def secure_register():
username = request.form.get('username')
password = request.form.get('password')
# Fixed: Server-side validation
is_valid, errors = password_validator.validate(password, username)
if not is_valid:
return jsonify({'errors': errors}), 400
# Fixed: Strong password hashing
password_hash = bcrypt.hashpw(
password.encode(),
bcrypt.gensalt(rounds=12)
)
save_user(username, password_hash)
return jsonify({'message': 'User registered successfully'})
// Fixed: Server-side password validation in Node.js
const bcrypt = require('bcrypt');
const fetch = require('node-fetch');
const crypto = require('crypto');
class SecurePasswordValidator {
static MIN_LENGTH = 12;
static MAX_LENGTH = 128;
static BCRYPT_ROUNDS = 12;
constructor() {
this.commonPasswords = new Set(/* load from file */);
}
async validate(password, username = null) {
const errors = [];
if (!password) {
return { valid: false, errors: ['Password is required'] };
}
// Fixed: Length requirements
if (password.length < SecurePasswordValidator.MIN_LENGTH) {
errors.push(`Password must be at least ${SecurePasswordValidator.MIN_LENGTH} characters`);
}
if (password.length > SecurePasswordValidator.MAX_LENGTH) {
errors.push(`Password must not exceed ${SecurePasswordValidator.MAX_LENGTH} characters`);
}
// Fixed: Block common passwords
if (this.commonPasswords.has(password.toLowerCase())) {
errors.push('This password is too common');
}
// Fixed: Block username in password
if (username && password.toLowerCase().includes(username.toLowerCase())) {
errors.push('Password cannot contain your username');
}
// Fixed: Character variety
if (!this.hasVariety(password)) {
errors.push('Password should include a mix of character types');
}
// Fixed: Check against breached passwords
if (await this.isBreached(password)) {
errors.push('This password has been exposed in data breaches');
}
return { valid: errors.length === 0, errors };
}
hasVariety(password) {
const checks = [
/[a-z]/.test(password),
/[A-Z]/.test(password),
/\d/.test(password),
/[!@#$%^&*(),.?":{}|<>]/.test(password)
];
return checks.filter(Boolean).length >= 3;
}
async isBreached(password) {
const sha1 = crypto.createHash('sha1').update(password).digest('hex').toUpperCase();
const prefix = sha1.substring(0, 5);
const suffix = sha1.substring(5);
const response = await fetch(`https://api.pwnedpasswords.com/range/${prefix}`);
const text = await response.text();
return text.includes(suffix);
}
}
// Fixed: Secure Express.js registration
const validator = new SecurePasswordValidator();
app.post('/register', async (req, res) => {
const { username, password } = req.body;
// Fixed: Server-side validation
const result = await validator.validate(password, username);
if (!result.valid) {
return res.status(400).json({ errors: result.errors });
}
// Fixed: Strong hashing
const hash = await bcrypt.hash(password, SecurePasswordValidator.BCRYPT_ROUNDS);
await createUser(username, hash);
res.json({ success: true });
});
CVE Examples
- CVE-2020-4574: IBM Key Management Server does not require strong passwords by default, allowing attackers to more easily compromise user accounts through weak credentials.
References
- MITRE Corporation. "CWE-521: Weak Password Requirements." https://cwe.mitre.org/data/definitions/521.html
- NIST SP 800-63B. "Digital Identity Guidelines: Authentication and Lifecycle Management."
- OWASP. "Authentication Cheat Sheet."