Description
A weak password policy (CWE-521) exists when an application imposes insufficient constraints on user-chosen passwords, allowing the creation of credentials that are easily guessed or cracked. This includes accepting very short passwords, not requiring character complexity, permitting well-known passwords from breach datasets, and failing to limit password reuse. The vulnerability falls under OWASP A07:2021 (Identification and Authentication Failures) and directly enables downstream attacks including brute force, credential stuffing, and offline cracking of stolen hashes.
Password policy weaknesses are endemic in enterprise applications and consumer-facing services alike. Despite years of guidance, applications continue to require only 6-8 character minimums without length maximums, accept passwords like Password1! that technically satisfy complexity rules while being trivially guessable, and fail to check submitted passwords against breach databases containing billions of previously exposed credentials.
NIST SP 800-63B (2017) significantly updated the guidance on password policy, recommending length over complexity, mandatory checking against known-compromised passwords, and eliminating arbitrary composition rules that lead users toward predictable patterns. Applications still following pre-2017 policies are misaligned with current best practices and more vulnerable as a result.
How It Works
A penetration tester discovers that an application accepts Password1 as a valid password — exactly 9 characters, uppercase, lowercase, digit. This satisfies most legacy complexity rules. A targeted dictionary attack against this application would succeed quickly because:
rockyou.txt contains "password" and common variants
Adding "1" to a dictionary word is one of the top 10 mutation rules in Hashcat
Testing the policy systematically:
# Test minimum length
curl -X POST /api/register -d '{"password": "a"}' → Accept/Reject?
# Test against known bad passwords
curl -X POST /api/register -d '{"password": "password123"}' → Accept/Reject?
# Test for maximum length enforcement (some apps truncate hashes)
curl -X POST /api/register -d '{"password": "AAAA...1000 chars"}' → Accept/Reject?
Offline cracking after hash theft — if the application stores passwords with a weak algorithm (MD5, SHA1, unsalted SHA256) or a fast hash (bcrypt with cost factor 4), dictionary attacks with Hashcat become trivial:
# Hashcat with rockyou wordlist and best64 rules against MD5 hashes
hashcat -m 0 -a 0 hashes.txt rockyou.txt -r best64.rule
Against MD5(password123) this cracks in milliseconds. Against bcrypt($cost=12, password123), the same attack would take years.
Common weak passwords that satisfy naive complexity rules:
Password1!— dictionary word, uppercase, digit, symbolSummer2024!— seasonal pattern, extremely commonCompany@123— company name substitution, common in enterprise environmentsWelcome1— default first-login password at many organizations
Impact
- Account Takeover via Brute Force — Short or simple passwords are cracked within seconds to minutes using dictionary attacks
- Credential Stuffing Success — Weak passwords are more likely to match credentials from previous breaches, enabling credential stuffing at scale
- Password Spray Vulnerability — Common passwords like
Spring2024!succeed in low-and-slow spray attacks that avoid lockout thresholds - Offline Cracking After Breach — Weak passwords are recovered almost immediately when a hash database is stolen, multiplying the impact of any future breach
- Compliance Failures — Many regulatory frameworks (PCI DSS, HIPAA, SOC 2) require demonstrable password strength controls
Detection
- Test minimum password length — attempt to register or change password to 1, 4, 6, and 8 character passwords. Note the minimum accepted length.
- Test against known-bad passwords — submit
password,password123,12345678,qwerty123, andWelcome1. If any are accepted, the application lacks breach password checking. - Verify no maximum length truncation — submit a 500-character password and verify it is stored in full (test by logging in with the full string vs. a truncated version). Truncation indicates the hash input is being truncated before hashing.
- Check password reuse enforcement — change the password back to the previous value. Many applications claim to enforce history but don't.
- Test complexity rule bypass — submit passwords that technically satisfy rules but are trivially guessable:
Password1,Passw0rd,Test1234!. - Verify lockout policy interaction — confirm that a weak password policy combined with no account lockout creates a brute-forceable login endpoint.
Remediation
Enforce a minimum length of 12 characters (NIST recommends 15+ for privileged accounts). Drop arbitrary complexity rules that drive users toward predictable patterns.
Check passwords against breach databases. Use the Have I Been Pwned (HIBP) Pwned Passwords API (which uses k-anonymity to protect the submitted password) to reject credentials appearing in known breach dumps:
const axios = require('axios');
const crypto = require('crypto');
async function isPwned(password) {
const hash = crypto.createHash('sha1').update(password).digest('hex').toUpperCase();
const prefix = hash.slice(0, 5);
const response = await axios.get(`https://api.pwnedpasswords.com/range/${prefix}`);
return response.data.includes(hash.slice(5));
}
Use a slow hashing algorithm. Store passwords with Argon2id (recommended), bcrypt (cost ≥ 12), or scrypt. Never use MD5, SHA1, or unsalted hashes.
Implement a password strength meter. Use zxcvbn (Dropbox's password strength estimator) to provide real-time feedback and block passwords below a minimum strength score, regardless of whether they satisfy complexity rules.
Allow long passwords. Accept passwords up to at least 64 characters (the bcrypt 72-byte limit should be handled transparently, e.g., by pre-hashing with SHA-256 before bcrypt).
