Skip to main content

Account lockout

Need

Prevention of unauthorized account lockouts

Context

  • Usage of Java 8 for developing applications with enhanced features and performance
  • Usage of javax.servlet-api for developing Java web applications with Servlets
  • Usage of JavaEE API for building enterprise Java applications

Description

Non compliant code

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpSession;

public class LoginServlet extends javax.servlet.http.HttpServlet {
private static final long serialVersionUID = 1L;

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

User user = Database.getUser(username);

if (user != null && user.getPassword().equals(password)) {
HttpSession session = request.getSession();
session.setAttribute("user", user);
response.sendRedirect("home.jsp");
} else {
response.sendRedirect("error.jsp");
}
}
}

The above code is a simple servlet that handles user login. When a POST request is made to this servlet with a username and password, it checks the database for a user with the provided username and compares the provided password with the one stored in the database.

The vulnerability lies in the lack of account lockout mechanism. In the current implementation, an attacker can continuously attempt to login with different passwords until the correct one is found. This is known as a brute force attack. Since there is no account lockout after a certain number of failed attempts, the system is vulnerable to such attacks.

Moreover, the system does not distinguish between a user that does not exist and an incorrect password for an existing user. This allows an attacker to determine if a user exists in the system by observing the response of the server.

Overall, the lack of an account lockout mechanism and indistinguishable responses for non-existing users and incorrect passwords make this code vulnerable to brute force and username enumeration attacks.

Steps

  • Implement account lockout mechanism
  • Limit the number of login attempts
  • Implement a delay between login attempts
  • Notify the user about account lockout

Compliant code

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpSession;

public class LoginServlet extends javax.servlet.http.HttpServlet {
private static final long serialVersionUID = 1L;
private static final int MAX_ATTEMPTS = 5;
private static final long LOCKOUT_TIME = 300000; // 5 minutes in milliseconds
private static HashMap<String, Integer> attempts = new HashMap<>();
private static HashMap<String, Long> lockout = new HashMap<>();

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

User user = Database.getUser(username);

if (user != null && user.getPassword().equals(password)) {
if (lockout.containsKey(username) && lockout.get(username) > System.currentTimeMillis()) {
response.sendRedirect("lockout.jsp");
return;
}

if (attempts.containsKey(username) && attempts.get(username) >= MAX_ATTEMPTS) {
lockout.put(username, System.currentTimeMillis() + LOCKOUT_TIME);
attempts.remove(username);
response.sendRedirect("lockout.jsp");
return;
}

HttpSession session = request.getSession();
session.setAttribute("user", user);
response.sendRedirect("home.jsp");
} else {
attempts.put(username, attempts.getOrDefault(username, 0) + 1);
response.sendRedirect("error.jsp");
}
}
}

The updated code now includes a mechanism to prevent account lockouts. This is achieved by limiting the number of login attempts and implementing a delay between login attempts.

A HashMap named attempts is used to keep track of the number of failed login attempts for each user. If a user fails to login MAX_ATTEMPTS times (5 in this case), their username is added to another HashMap named lockout, which stores the time at which they can attempt to login again.

When a user tries to login, the system first checks if their account is currently locked. If it is, they are redirected to a "lockout" page. If their account is not locked, but they have reached the maximum number of login attempts, their account is locked for a certain amount of time (LOCKOUT_TIME, 5 minutes in this case) and they are redirected to the "lockout" page.

If a user successfully logs in, their failed login attempts are reset and they are redirected to the "home" page. If they fail to login, the number of failed login attempts for their username is incremented and they are redirected to an "error" page.

References