Skip to main content

Overview

This vulnerability occurs when the mechanism for users to recover or reset forgotten passwords is insecure. Common weaknesses include:
  • Predictable Reset Tokens: Generating password reset tokens that are short, based on guessable information (like username or timestamp), or have insufficient randomness, allowing attackers to predict or brute-force them.
  • Token Transmission over Insecure Channels: Sending reset tokens or temporary passwords via unencrypted email (HTTP links) or SMS.
  • Information Leakage: The recovery process reveals whether a username or email exists in the system (“User Enumeration”).
  • Weak Security Questions: Relying on easily guessable or publicly available answers to “secret questions”.
  • Token Reuse/No Expiry: Reset tokens do not expire or can be reused after the password has been reset.
  • Not Invalidating Other Sessions: Failing to log the user out of other active sessions after a password reset. 🤔❓🔑

Business Impact

Weak password recovery mechanisms provide a direct path for attackers to compromise user accounts:
  • Account Takeover: Attackers can guess or predict reset tokens/answers, allowing them to set a new password and take over the account.
  • User Enumeration: Attackers can determine valid usernames or emails registered with the service.
  • Loss of Trust: Users rely on secure recovery processes; flaws severely damage trust.

Reference Details

CWE ID: CWE-640 OWASP Top 10 (2021): A07:2021 - Identification and Authentication Failures Severity: High to Critical

Framework-Specific Analysis and Remediation

Password recovery typically involves generating a secure, random, single-use, time-limited token, sending it to the user via a secure channel (usually email with an HTTPS link), and requiring the user to present that token to set a new password. Frameworks often provide built-in modules for this. Key Remediation Principles:
  1. Use Strong Tokens: Generate long (e.g., 32+ bytes), cryptographically random tokens (CSPRNG). Store a hash of the token in the database, not the token itself.
  2. Set Token Expiry: Tokens should expire after a short period (e.g., 15-60 minutes). Store the expiry timestamp with the token hash.
  3. Single Use: Invalidate the token immediately after it is successfully used.
  4. Secure Transmission: Send reset links via email using HTTPS URLs. Avoid sending temporary passwords directly.
  5. Avoid User Enumeration: Respond with a generic message like “If an account exists for this email, a reset link has been sent” regardless of whether the email was found.
  6. Avoid Weak Security Questions: Do not use easily guessable questions. Prefer token-based email/SMS reset.
  7. Invalidate Sessions: Log the user out of all other active sessions upon successful password reset.

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

Framework Context

Using Django’s built-in PasswordResetView and associated forms/tokens, or custom Flask logic.

Vulnerable Scenario 1: Predictable Token Generation (Custom)

# accounts/utils.py
import hashlib
import time
from django.conf import settings

def generate_weak_reset_token(user):
    # DANGEROUS: Token based on predictable data (user ID, timestamp)
    # and a weak hash (MD5) without strong randomness.
    timestamp = int(time.time())
    data_to_hash = f"{user.id}:{user.password}:{timestamp}:{settings.SECRET_KEY}"
    token = hashlib.md5(data_to_hash.encode()).hexdigest()
    # Assume token and timestamp stored temporarily
    return token, timestamp

Vulnerable Scenario 2: Token Sent Over HTTP

The email template for the password reset link uses an http:// URL.
Please click here to reset your password:
<a href="http://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}">Reset Link</a>

Mitigation and Best Practices

  • Django: Use the built-in authentication views (PasswordResetView, PasswordResetConfirmView, etc.) which rely on django.contrib.auth.tokens.PasswordResetTokenGenerator. This generator creates secure, time-limited, single-use tokens based on user state and the application’s SECRET_KEY.
  • Ensure email templates use https:// links.

Secure Code Example

# urls.py (Using Django's built-in views)
from django.contrib.auth import views as auth_views

urlpatterns = [
    # SECURE: Use Django's built-in password reset flow.
    path('reset_password/',
         auth_views.PasswordResetView.as_view(template_name="accounts/password_reset.html"),
         name="reset_password"),
    path('reset_password/sent/',
         auth_views.PasswordResetDoneView.as_view(template_name="accounts/password_reset_sent.html"),
         name="password_reset_done"),
    path('reset/<uidb64>/<token>/',
         auth_views.PasswordResetConfirmView.as_view(template_name="accounts/password_reset_form.html"),
         name="password_reset_confirm"),
    path('reset_password/complete/',
         auth_views.PasswordResetCompleteView.as_view(template_name="accounts/password_reset_done.html"),
         name="password_reset_complete"),
]
Please click here to reset your password:
<a href="https://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}">Reset Link</a>

Testing Strategy

Review the token generation logic. Is it using cryptographically secure randomness (secrets.token_urlsafe) or a robust framework mechanism (like Django’s)? Check token length and expiry. Attempt to reuse a reset token after successfully resetting the password. Check the reset link email for http:// vs https://. Test the “forgot password” form with known and unknown email addresses; verify the response message is generic and doesn’t reveal account existence.