Skip to main content

Account Takeover

Need

Enhanced account security and protection against unauthorized access

Context

  • Usage of Scala 2.13.3 for building scalable and efficient 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
  • Usage of javax.inject.Inject for dependency injection in Java

Description

Non compliant code

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

case class User(username: String, password: String)

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

class UserController @Inject()(cc: ControllerComponents) extends AbstractController(cc) {
def login = Action { implicit request =>
val user = userForm.bindFromRequest.get
// Assume users is a Map of usernames to passwords
val users = Map("admin" -> "password123")
if (users.get(user.username).contains(user.password)) {
Ok("Logged in").withSession("username" -> user.username)
} else {
Unauthorized("Invalid username or password")
}
}
}

The above Scala code snippet is a simple login system using the Play framework. It defines a User case class with a username and password, a form to bind request data to a User instance, and a UserController with a login action.

The vulnerability lies in the way the login action handles authentication. It simply checks if the submitted username exists in a predefined users map and if the corresponding password matches the submitted one. If both checks pass, the username is stored in the session and a successful login message is returned. Otherwise, an unauthorized error is returned.

This approach is vulnerable to account takeover attacks due to several reasons:

  1. Insecure Direct Object References (IDOR): The application directly uses the input from the user to fetch the password from the users map. An attacker could potentially manipulate the input to reference a different object than intended.

  2. Lack of Password Hashing: The passwords are stored in plain text in the users map. This is a security risk as anyone with access to the map can see the passwords.

  3. No Account Lockout: The application does not implement an account lockout mechanism after a certain number of failed login attempts. This makes it susceptible to brute force attacks, where an attacker tries many different passwords until the correct one is found.

  4. Session Management: The application stores the username in the session after a successful login. If an attacker can somehow steal a user's session (e.g., via session sidejacking or XSS), they can impersonate the user.

Steps

  • Implement a secure password storage mechanism, such as hashing and salting, to protect user passwords.
  • Use a secure session management mechanism to handle user sessions.
  • Implement strong authentication mechanisms, such as multi-factor authentication, to prevent unauthorized access to user accounts.
  • Implement secure input validation to prevent injection attacks and other forms of input manipulation.
  • Regularly update and patch the application and its dependencies to address any known vulnerabilities.
  • Implement secure coding practices, such as input/output validation, to prevent common security vulnerabilities.
  • Perform regular security testing and code reviews to identify and fix any potential vulnerabilities.
  • Educate developers about secure coding practices and provide training on common security vulnerabilities.
  • Implement proper access controls and authorization mechanisms to ensure that users can only perform actions they are authorized to do.
  • Implement secure password recovery mechanisms to prevent unauthorized access to user accounts.

Compliant code

import play.api.mvc._
import play.api.data._
import play.api.data.Forms._
import org.mindrot.jbcrypt.BCrypt

case class User(username: String, password: String)

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

class UserController @Inject()(cc: ControllerComponents) extends AbstractController(cc) {
def login = Action { implicit request =>
val user = userForm.bindFromRequest.get
// Assume users is a Map of usernames to hashed passwords
val users = Map("admin" -> BCrypt.hashpw("password123", BCrypt.gensalt()))
if (users.get(user.username).exists(hashedPassword => BCrypt.checkpw(user.password, hashedPassword))) {
Ok("Logged in").withNewSession.withSession("username" -> user.username)
} else {
Unauthorized("Invalid username or password")
}
}
}

The original code was vulnerable to account takeover due to insecure password storage and session management. The password was stored in plaintext and the session was not properly managed.

The fixed code now uses the BCrypt library to hash and salt the password before storing it. This means that even if an attacker gains access to the password storage, they will not be able to reverse-engineer the original password from the hash.

The session management has also been improved. Instead of using withSession, which keeps the old session data, withNewSession is used to create a new session every time a user logs in. This reduces the risk of session fixation attacks.

However, this is still a basic example and real-world applications should implement more robust security measures. For example, the user data should be stored in a secure database, not a simple Map. The application should also implement secure input validation, multi-factor authentication, regular security updates and patches, secure coding practices, regular security testing and code reviews, developer education, proper access controls and authorization, and secure password recovery mechanisms.

References