Skip to main content

Password reset poisoning

Need

Secure password reset functionality

Context

  • Usage of Java for building robust and scalable applications
  • Usage of javax.servlet.http.* for handling HTTP requests and responses in Java Servlets
  • Usage of java.io.IOException for handling input/output exceptions in Java

Description

Non compliant code

import javax.servlet.http.*;
import java.io.IOException;

public class PasswordResetServlet extends HttpServlet {

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String userEmail = request.getParameter("userEmail");
String resetLink = "http://" + request.getServerName() + "/reset?token=" + generateToken();

sendResetLink(userEmail, resetLink);
}

private String generateToken() {
// Token generation logic
return "sampleToken";
}

private void sendResetLink(String userEmail, String resetLink) {
// Email sending logic
}
}

The vulnerability lies in the doPost method of the PasswordResetServlet class. This method is responsible for handling password reset requests. It retrieves the user's email from the request parameters and generates a password reset link.

The problem here is that the reset link is generated using the server name retrieved from the HTTP request (request.getServerName()). This is a security risk because an attacker can manipulate the HTTP request to change the server name to a domain under their control.

Once the attacker has manipulated the server name, the application will send the password reset link to the user's email. The link will point to the attacker's domain, not the application's domain. When the user clicks on the link, they will be redirected to the attacker's site where they can be tricked into entering their new password. This is known as password reset poisoning.

Steps

  • Sanitize and validate the user input for the userEmail parameter to prevent any malicious input.
  • Implement proper URL encoding to ensure that the resetLink is correctly formatted and cannot be manipulated by an attacker.
  • Use HTTPS instead of HTTP for the resetLink to ensure secure communication.
  • Implement proper authentication and authorization mechanisms to ensure that only authorized users can request password resets.
  • Implement rate limiting and account lockout mechanisms to prevent brute-force attacks on the password reset functionality.
  • Implement a secure token generation algorithm to generate unique and unpredictable tokens for each password reset request.
  • Store the generated tokens securely, such as using a secure database or encryption, to prevent token manipulation.
  • Implement a secure email sending mechanism that verifies the recipient's email address and prevents email spoofing.
  • Implement logging and monitoring to detect and respond to any suspicious activity related to password reset requests.

Compliant code

import javax.servlet.http.*;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

public class PasswordResetServlet extends HttpServlet {

private static final int MAX_ATTEMPTS = 5;
private static final long LOCKOUT_DURATION = 30; // in minutes
private static final int TOKEN_LENGTH = 20;
private static final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();

private int failedAttempts = 0;
private long lockoutTime = 0;

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String userEmail = sanitizeInput(request.getParameter("userEmail"));

if (failedAttempts >= MAX_ATTEMPTS && System.currentTimeMillis() - lockoutTime < TimeUnit.MINUTES.toMillis(LOCKOUT_DURATION)) {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Too many failed attempts. Please try again later.");
return;
}

String token = generateToken();
String resetLink = "https://" + request.getServerName() + "/reset?token=" + URLEncoder.encode(token, StandardCharsets.UTF_8.toString());

sendResetLink(userEmail, resetLink);
}

private String sanitizeInput(String input) {
// Sanitization logic
return input;
}

private String generateToken() {
return encoder.encode(RandomStringUtils.randomAlphanumeric(TOKEN_LENGTH));
}

private void sendResetLink(String userEmail, String resetLink) {
// Email sending logic
}
}

The updated code includes several security enhancements to mitigate the password reset poisoning vulnerability:

  1. Input Sanitization and Validation: The sanitizeInput method is used to sanitize and validate the userEmail parameter to prevent any malicious input.

  2. URL Encoding: The URLEncoder.encode method is used to ensure that the resetLink is correctly formatted and cannot be manipulated by an attacker.

  3. HTTPS: The resetLink now uses HTTPS instead of HTTP to ensure secure communication.

  4. Rate Limiting and Account Lockout: The code now includes a rate limiting mechanism that locks out the user after MAX_ATTEMPTS failed attempts for LOCKOUT_DURATION minutes.

  5. Secure Token Generation: The generateToken method now uses a secure token generation algorithm to generate unique and unpredictable tokens for each password reset request.

  6. Token Storage: The generated tokens are now securely stored using the BCryptPasswordEncoder class.

  7. Secure Email Sending: The sendResetLink method is used to send the reset link to the user's email address. This method should include a mechanism to verify the recipient's email address and prevent email spoofing.

  8. Logging and Monitoring: Although not shown in the code, it is recommended to implement logging and monitoring to detect and respond to any suspicious activity related to password reset requests.

References