Skip to main content

Overview

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. 💪❌

Business Impact

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-521 OWASP Top 10 (2021): A07:2021 - Identification and Authentication Failures Severity: Medium

Framework-Specific Analysis and Remediation

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:
  1. Enforce Minimum Length: Require passwords to be reasonably long (e.g., 12 characters minimum, longer is better).
  2. 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.
  3. Check Against Common Passwords: Use a blocklist of common passwords (e.g., top 10,000 lists) and reject them.
  4. 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.
  5. Check Against Contextual Information: Prevent users from using their username, email, name, or application name as their password.
  6. Provide Feedback: Clearly inform users about the password requirements.
  7. Server-Side Enforcement: All password policy checks must be performed server-side. Client-side checks are for usability only.

  • Python
  • Java
  • .NET(C#)
  • PHP
  • Node.js
  • Ruby

Framework Context

Using Django’s Authentication system validators (AUTH_PASSWORD_VALIDATORS in settings.py). Flask requires manual implementation or extensions.

Vulnerable Scenario 1: No Validators in Django

# settings.py
# DANGEROUS: AUTH_PASSWORD_VALIDATORS is missing or commented out.
# Django will accept any password, including very weak ones.
# AUTH_PASSWORD_VALIDATORS = [ ... ]

Vulnerable Scenario 2: Manual Check Missing Complexity (Flask)

# forms.py (Flask WTForms example)
from wtforms import Form, StringField, PasswordField, validators

class RegistrationForm(Form):
    username = StringField('Username', [validators.Length(min=4, max=25)])
    password = PasswordField('New Password', [
        validators.DataRequired(),
        # DANGEROUS: Only checks length, not complexity or common passwords.
        validators.Length(min=8)
    ])
# Assume route handler uses this form but performs no further password checks.

Mitigation and Best Practices

  • 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.

Secure Code Example

# 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 exist

def 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.

Testing Strategy

Attempt to register or change passwords using values that violate the intended policy:
  • Too short (e.g., “Pass1!”)
  • Missing complexity (e.g., “passwordpassword”, “123456789012”, “PASSWORDPASSWORD”)
  • Common passwords (“password”, “123456”, “admin”)
  • Contextual passwords (username, email prefix, application name) Verify that the server-side validation rejects weak passwords with appropriate error messages. Check Django AUTH_PASSWORD_VALIDATORS setting. Review custom validation logic.