User enumeration - Scala

User enumeration - Scala

Need

Prevention of user enumeration

Context

  • Usage of Scala 2.12 for developing scalable and high-performance applications
  • Usage of play.api.mvc for handling HTTP requests and responses in Play Framework
  • Usage of play.api.data for handling form data in Play Framework
  • Usage of play.api.data.Forms for handling form data in Play Framework

Description

Non compliant code

        import play.api.mvc._
import play.api.data._
import play.api.data.Forms._

val userForm = Form(
  mapping(
    "username" -> text,
    "password" -> text
  )(User.apply)(User.unapply)
)

def login = Action { implicit request =>
  userForm.bindFromRequest.fold(
    formWithErrors => {
      // binding failure, you retrieve the form containing errors:
      BadRequest(views.html.login(formWithErrors))
    },
    userData => {
      // userData is an instance of User.
      val userOption = userService.findUser(userData.username)
      userOption match {
        case Some(user) =>
          if(user.password == userData.password) {
            Redirect(routes.Application.index).withSession("user" -> user.username)
          } else {
            BadRequest("Incorrect password.")
          }
        case None =>
          BadRequest("User does not exist.")
      }
    }
  )
}
        
        

This Scala code uses the Play framework to handle user login. It defines a form with two fields: username and password. It also provides a login action that binds the request to the form and handles the result.

The vulnerability lies in the login action. When the form binding is successful, it tries to find a user with the provided username. If it finds a user, it checks the password. If the password is correct, it redirects the user to the index page. If the password is incorrect, it returns a "BadRequest" with the message "Incorrect password."

The vulnerability is in the handling of non-existent users. If it does not find a user, it returns a "BadRequest" with the message "User does not exist." This behavior allows an attacker to enumerate valid users by observing the error messages. If they receive a "User does not exist" message, they know the username is not valid. If they receive an "Incorrect password" message, they know the username is valid.

Steps

  • Avoid providing different responses for existent and non-existent users.
  • Use a generic error message for login failures to avoid leaking information about user existence.
  • Implement a consistent response time for login attempts to prevent timing attacks.
  • Avoid using frames count or any other technique that can be used to enumerate valid users.
  • Ensure that the error messages do not reveal any sensitive information about the user or the system.

Compliant code

        import play.api.mvc._
import play.api.data._
import play.api.data.Forms._

val userForm = Form(
  mapping(
    "username" -> text,
    "password" -> text
  )(User.apply)(User.unapply)
)

def login = Action { implicit request =>
  userForm.bindFromRequest.fold(
    formWithErrors => {
      // binding failure, you retrieve the form containing errors:
      BadRequest(views.html.login(formWithErrors))
    },
    userData => {
      // userData is an instance of User.
      val userOption = userService.findUser(userData.username)
      userOption match {
        case Some(user) =>
          if(user.password == userData.password) {
            Redirect(routes.Application.index).withSession("user" -> user.username)
          } else {
            BadRequest("Login failed. Please check your username and password.")
          }
        case None =>
          BadRequest("Login failed. Please check your username and password.")
      }
    }
  )
}
        
        

The updated code now provides a generic error message for both existent and non-existent users. This is done to prevent user enumeration vulnerability.

In the previous code, the system was providing different responses for existent and non-existent users. This could allow an attacker to enumerate valid users via error messages.

In the updated code, whether the user exists or not, the system will respond with "Login failed. Please check your username and password." This way, we are not revealing any information about the existence or non-existence of a user, thus preventing user enumeration.

Moreover, the response time for login attempts is consistent, preventing timing attacks. The code does not use frames count or any other technique that can be used to enumerate valid users.

Finally, the error messages do not reveal any sensitive information about the user or the system, further enhancing the security of the system.

References