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

ImpactDetails
Access ControlScope: 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.
ConfidentialityScope: 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

  1. MITRE Corporation. "CWE-521: Weak Password Requirements." https://cwe.mitre.org/data/definitions/521.html
  2. NIST SP 800-63B. "Digital Identity Guidelines: Authentication and Lifecycle Management."
  3. OWASP. "Authentication Cheat Sheet."