This vulnerability occurs when an application fails to enforce strong password policies during user registration or password changes. Weak requirements might allow users to set passwords that are:
Too short (e.g., less than 12 characters).
Lacking complexity (e.g., not requiring a mix of uppercase, lowercase, numbers, and symbols).
Commonly used (e.g., “password”, “123456”, “qwerty”).
Related to user context (e.g., username, email, real name).
Previously breached (found in known data breach lists).
Attackers can easily guess or crack weak passwords using dictionary attacks, brute-force, or lists of breached credentials. 💪❌
Allowing weak passwords significantly increases the risk of:
Account Takeover: Attackers can easily guess user passwords, leading to unauthorized access.
Credential Stuffing Success: If users reuse weak passwords across multiple sites, a breach elsewhere makes accounts on your site vulnerable.
Reputational Damage: Users expect applications to enforce basic password security; failing to do so reflects poorly on the application’s security posture.
Reference Details
CWE ID:CWE-521OWASP Top 10 (2021): A07:2021 - Identification and Authentication Failures
Severity: Medium
Password policies are usually enforced during user registration and password update operations. Modern frameworks often provide built-in validators or configuration options for setting complexity requirements.Key Remediation Principles:
Enforce Minimum Length: Require passwords to be reasonably long (e.g., 12 characters minimum, longer is better).
Enforce Complexity: Require a mix of character types (uppercase, lowercase, numbers, symbols). NIST guidelines (SP 800-63B) suggest length is more important than forced complexity, but complexity remains a common defense-in-depth measure.
Check Against Common Passwords: Use a blocklist of common passwords (e.g., top 10,000 lists) and reject them.
Check Against Breached Passwords: Integrate with services like “Have I Been Pwned” (HIBP) Pwned Passwords API to check if the chosen password has appeared in known data breaches.
Check Against Contextual Information: Prevent users from using their username, email, name, or application name as their password.
Provide Feedback: Clearly inform users about the password requirements.
Server-Side Enforcement:All password policy checks must be performed server-side. Client-side checks are for usability only.
# settings.py# DANGEROUS: AUTH_PASSWORD_VALIDATORS is missing or commented out.# Django will accept any password, including very weak ones.# AUTH_PASSWORD_VALIDATORS = [ ... ]
Django: Configure AUTH_PASSWORD_VALIDATORS in settings.py with built-in validators (UserAttributeSimilarityValidator, MinimumLengthValidator, CommonPasswordValidator, NumericPasswordValidator). Consider adding custom validators or third-party packages (like django-pwned-passwords) for breached password checks.
Flask: Implement server-side checks in the route handler or user service layer. Check length, complexity (using regex or character class counts), against common password lists, and optionally against the HIBP API.
# settings.py (Django - Secure Validators)# SECURE: Configure a strong set of validators.AUTH_PASSWORD_VALIDATORS = [ { # Checks against username, email, first_name, last_name 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { # Enforces minimum length 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 'OPTIONS': { 'min_length': 12, # SECURE: Increased minimum length } }, { # Checks against a built-in list of common passwords 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { # Prevents purely numeric passwords 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, # Optional: Add django-pwned-passwords validator here # { # 'NAME': 'pwned_passwords_django.validators.PwnedPasswordValidator', # },]
# services/auth_service.py (Flask - Secure Custom Validation)import re# Assume check_if_common_password and check_if_pwned existdef is_password_strong(password, username, email): if len(password) < 12: return False, "Password must be at least 12 characters long." # SECURE: Example complexity check (at least 1 upper, 1 lower, 1 digit, 1 symbol) if not re.search(r"[A-Z]", password): return False, "Requires uppercase." if not re.search(r"[a-z]", password): return False, "Requires lowercase." if not re.search(r"\d", password): return False, "Requires digit." if not re.search(r"[!@#$%^&*()_+=\-{}\[\]:;\"'|\\<>,.?/~`]", password): return False, "Requires symbol." # SECURE: Check against common passwords if check_if_common_password(password): return False, "Password is too common." # SECURE: Check against user context if username.lower() in password.lower() or email.split('@')[0].lower() in password.lower(): return False, "Password cannot contain username or email parts." # SECURE: Check against breached passwords (e.g., using HIBP API) # if check_if_pwned(password): # return False, "Password has appeared in a data breach." return True, "Password meets requirements."# Flask route handler would call is_password_strong before saving.
Using Bean Validation (javax.validation.constraints) annotations on DTOs, custom validation logic in services, or Spring Security’s password policy features (less common directly).
A UserRegistrationDto doesn’t have annotations to check password complexity.
// dto/UserRegistrationDto.javapublic class UserRegistrationDto { private String username; // DANGEROUS: Only basic length check, if any. No complexity. // Needs @Pattern or custom validator. @Size(min = 8, message = "Password must be at least 8 characters") private String password; // ... getters/setters ...}// Controller accepts this DTO but relies only on @Size.
Bean Validation: Use @Size for length and @Pattern with a strong regex for complexity on DTO fields. Implement custom ConstraintValidator classes for more complex rules (like checking against common lists, HIBP, or user context).
Service Layer: Perform validation within the user service before hashing and saving the password.
Attempt registration/password changes with passwords violating length, complexity, common lists, and contextual rules. Ensure @Valid is used in controllers and DTOs have appropriate @Size, @Pattern, and custom constraint annotations. Verify server-side rejection.
Custom Validators: If needed, implement IPasswordValidator<TUser> to add checks (e.g., against common lists, HIBP, user context) in addition to Identity’s built-in options. Register multiple validators if necessary.
Attempt registration/password changes violating the configured Identity options (length, digits, upper/lower, non-alphanumeric). Verify rejection by Identity. If custom validators are used, test their specific logic (common passwords, contextual checks, breached passwords).
<?php// register.php$password = $_POST['password'];// DANGEROUS: Minimal check, allows weak passwords.if (strlen($password) < 6) { die("Password must be at least 6 characters.");}// No other checks performed before hashing and saving.// ... hash password and save user ...?>
Laravel: Use built-in validation rules like Password::min(12)->mixedCase()->numbers()->symbols()->uncompromised() (checks HIBP). Chain rules for length, complexity, and breached status in your Form Request or controller validation.
Plain PHP: Implement robust checks manually: length (strlen), complexity (regex preg_match for character classes), common lists (check against a file/array), contextual checks, and optionally HIBP API.
Attempt registration/password changes violating the defined Laravel validation rules or custom PHP checks (length, complexity, common, contextual). Verify server-side rejection. Use known breached passwords to test the uncompromised() rule if implemented.
// services/userService.jsfunction validatePassword(password) { // DANGEROUS: Simple regex only checks for length and maybe one char type. const weakRegex = /.{8,}/; // Example: Just length if (!password || !weakRegex.test(password)) { throw new Error("Password does not meet requirements."); } // Missing other checks...}
Attempt registration/password changes violating the configured rules (length, complexity via regex, custom checks). Verify server-side rejection with appropriate messages. Test common and contextual passwords.
Active Record: Add custom validate methods or use validates_format_of with strong regex for complexity. Implement checks against common lists, context (username, email), and optionally HIBP API.
Devise: Configure password length (config.password_length in devise.rb). Add gems like devise-security to enable more policies (complexity, breached passwords via HIBP).
Attempt registration/password changes violating the Active Record validations or Devise configuration (length, complexity, etc.). Verify server-side rejection. Test common, contextual, and (if implemented) known breached passwords.