Skip to main content

Free 30-min security demo  — We'll scan your real code and show live findings, no commitment Book Now

Offensive360
Academy Insecure Password Reset
Intermediate · 20 min

Insecure Password Reset

Identify guessable reset tokens, token reuse flaws, and user enumeration in password reset flows.

1 Password Reset Attack Vectors

Password reset mechanisms are high-value targets because they provide an alternative path to account takeover without knowing the password.

Guessable reset tokens:

# Vulnerable: time-based or sequential token
token = str(int(time.time()))  # Predictable!
token = hashlib.md5(email.encode()).hexdigest()  # Deterministic — no randomness!

Token not expiring: A reset token emailed to a user must expire (typically 15-60 minutes). If tokens never expire, an attacker can target old email breaches.

User enumeration via error messages:

# Vulnerable: reveals whether email exists
if not user_exists(email):
    return "No account found for this email"  # Attacker now knows!
return "Reset link sent"

Token not invalidated after use: A reset token used once should be immediately invalidated. If it can be reused, an attacker who intercepts the email link can reset the password again later.

2 Secure Password Reset Flow

Implement password reset securely with cryptographically random tokens, short expiry, and generic responses.

Cryptographically secure token:

import secrets
import hashlib

def generate_reset_token():
    token = secrets.token_urlsafe(32)  # 256 bits
    # Store the hash, not the raw token
    token_hash = hashlib.sha256(token.encode()).hexdigest()
    expiry = datetime.utcnow() + timedelta(minutes=30)
    db.store_reset_token(token_hash, expiry)
    return token  # Send raw token in email, store only hash

Generic response (no enumeration):

def request_reset(email):
    user = db.find_user(email)
    if user:  # Only send email if user exists
        token = generate_reset_token()
        send_reset_email(email, token)
    # ALWAYS return the same message
    return "If an account exists for this email, a reset link has been sent."

Defense checklist:

  • Use secrets.token_urlsafe(32) or equivalent
  • Expire tokens after 30 minutes
  • Invalidate tokens immediately after use
  • Return identical responses for valid and invalid emails
  • Rate-limit reset requests per IP and email

Knowledge Check

0/3 correct
Q1

Why is hashlib.md5(email.encode()).hexdigest() a bad password reset token?

Q2

Why should the server return the same response whether or not the email exists?

Q3

What security practice prevents token interception after password reset?

Code Exercise

Generate Secure Reset Token

The reset token generation uses MD5 of the email — predictable and unsafe. Rewrite it using secrets.token_urlsafe(32) and set a 30-minute expiry.

python