Introduction

Welcome to the "A04: Insecure Design" course! In this lesson, we will explore the topic of insecure credential recovery mechanisms, specifically focusing on the use of security questions and answers. Credential recovery is a critical component of web applications, allowing users to regain access to their accounts. However, when implemented insecurely, it can become a significant vulnerability.

Let's dive into understanding these mechanisms and how they can be exploited. 🚀

Historical Context of Security Questions

Security questions have been a popular authentication method since the early days of online banking and email services. The concept is simple: users choose predetermined questions and provide answers that only they should know. Common questions include "What's your mother's maiden name?", "What street did you grow up on?", or "What was your first pet's name?". While this method seemed secure initially, it has several fundamental flaws.

Other outdated recovery methods include:

  • SMS-based recovery (vulnerable to SIM swapping)
  • Simple PIN codes sent via email
  • Birth date verification
  • Last transaction amount (for banking)

The main security flaw with security questions is that the answers are often easily discoverable through social media, public records, or social engineering. This makes them particularly vulnerable to targeted attacks. Let's examine how these mechanisms typically work in code.

Understanding Credential Recovery Mechanisms

Credential recovery mechanisms are processes that allow users to regain access to their accounts when they forget their passwords. When implemented using security questions, the system compares user-provided answers against stored responses.

While this seems straightforward, it introduces several security risks because the answers are often predictable or publicly available information. Let's examine a vulnerable implementation that demonstrates these risks.

The Vulnerable Implementation

Let's examine a typical implementation of security question recovery, where the application first verifies the user exists and then checks their security answer. This is how the endpoint handles the initial recovery request:

This code immediately confirms whether a username exists in the system. Although hiding user existence provides maximum security, many applications choose to reveal this information but protect against enumeration attacks using CAPTCHA or rate limiting - so there is a trade-off that's worth exploring here.

Next, let's see how it handles the security answer verification:

This implementation is particularly vulnerable because it performs a simple string comparison and immediately returns a temporary password. Additionally, it lacks rate limiting, account lockouts, and stores security answers in plaintext, all of which are significant security issues. Now, let's see how an attacker might exploit these vulnerabilities.

Exploiting the Vulnerability

An attacker could easily create a script to attempt common pet names, especially if they know the security question is "What was your first pet's name?". Here's a simple demonstration of such an attack:

This script systematically tries common dog names against the recovery endpoint, and because the API reveals whether answers are correct, it can quickly find the right answer. Once successful, the attacker receives a temporary password in the response, which can be immediately used to log into the account and change the password permanently. To address these vulnerabilities, we need a more secure approach.

A More Secure Approach: Email-Based Token System

Instead of relying on security questions, a more secure approach is to implement an email-based password reset system. This approach has become an industry standard used by major platforms like Google, Amazon, and Microsoft due to its robust security model.

The security of this method is multi-faceted and addresses several key vulnerabilities present in security question systems:

  • Email accounts serve as a trusted secondary authentication channel, often protected by two-factor authentication (though not all users enable 2FA)
  • Time-limited tokens ensure reset links expire quickly, typically within an hour
  • The system generates cryptographically secure tokens that are practically impossible to guess
  • Every reset attempt creates an audit trail through email and application logs

These security features work together to create a robust system that's both secure and user-friendly, while providing security teams the visibility they need to detect and respond to potential attacks.

Let's examine an example implementation of this secure solution step by step.

Implementing Secure Password Reset

First, let's look at how we handle the initial password reset request. Here is an improved and robust implementation. (The generateResetToken utility function will be shown a bit later.)

Notice how we now return the same message regardless of whether the user exists, preventing username enumeration. This is done for maximum security, but as mentioned above you might want to reveal this information to users for better user experience.

Next, let's see how we generate and store the reset token.

Using Reset Tokens

The reset token mechanism is a crucial part of secure password recovery. The resetToken is typically a cryptographically secure random string that serves as a one-time password reset link. When stored in the database, it's associated with the user's account and given an expiration timestamp.

This implementation creates a secure, time-limited token and sends it via email. The token acts like a temporary key - it must be unique, unpredictable, and should expire after a short period (in this case, one hour) to minimize the window of opportunity for potential attacks.

Handling Password Reset Requests

When the user clicks the reset link, we verify the token and update the password:

Finally, we update the password and invalidate the token:

This implementation ensures that reset tokens are single-use and time-limited, significantly improving security over the security question approach.

Cryptographic Utilities for Secure Token Generation

To securely generate and store password reset tokens, use the following implementation:

This implementation uses crypto.randomBytes() for generating unpredictable tokens and SHA-256 for secure token hashing, ensuring both the generation and storage of reset tokens are cryptographically secure. The token is sent to the user while the tokenHash is stored in the database, similar to how we handle passwords.

Note that SHA-256 is suitable for hashing tokens, but for passwords, a slow hash like bcrypt should be used.

Conclusion and Next Steps

In this lesson, we explored the vulnerabilities of using security questions for credential recovery and demonstrated how they can be exploited. We also discussed and implemented a secure email-based token system as a better alternative.

As you move forward, you'll have the opportunity to practice these concepts in the exercises that follow. In the next lesson, we'll continue to build on this foundation by exploring other common vulnerabilities and their mitigations. Keep up the great work! 🌟

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal