Insecure functionality - Pass the hash - Scala

Insecure functionality - Pass the hash - Scala

Need

Secure authentication and authorization mechanisms

Context

  • Usage of Scala for building scalable and high-performance applications
  • Usage of play.api.mvc for handling HTTP requests and responses in Play Framework
  • Usage of play.api.libs.json for JSON parsing and manipulation in Play Framework

Description

Non compliant code

        import play.api.mvc._
import play.api.libs.json._

object Application extends Controller {

  def login = Action(parse.json) { request =>
    val username = (request.body \\ "username").as[String]
    val hash = (request.body \\ "hash").as[String]

    val user = User.authenticate(username, hash)

    if (user.isDefined) {
      Ok(Json.obj("status" -> "OK")).withSession("username" -> username, "hash" -> hash)
    } else {
      Unauthorized(Json.obj("status" -> "KO", "message" -> "Invalid username or password"))
    }
  }
}

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

object User {
  def authenticate(username: String, hash: String): Option[User] = {
    // Fetch the user from the database
    val user = Database.getUser(username)

    if (user.isDefined && user.get.passwordHash == hash) {
      Some(user.get)
    } else {
      None
    }
  }
}
        
        

In the above Scala code snippet, the application is using the Play framework to handle user authentication. This is done in the login action of the Application controller. The action reads a JSON request body and extracts a username and a password hash.

The password hash is then directly used to authenticate the user with the User.authenticate method. If the user's stored password hash matches the provided hash, the user is considered authenticated and a new session is created with the username and hash stored in it.

The vulnerability here is that the application is directly using the password hash for authentication, without any additional verification. This allows an attacker to use the "Pass The Hash" technique to gain unauthorized access to the system. If an attacker can obtain a user's password hash, they can send it in a login request to authenticate as that user, without needing to know the actual password.

Furthermore, the application is storing the password hash in the session data. This could potentially expose the hash to an attacker if the session data is not properly protected.

Steps

  • Replace the usage of password hash with a more secure authentication mechanism, such as bcrypt or Argon2.
  • Implement a secure password hashing function to store the user's password in the database.
  • Use a secure session management mechanism instead of storing the username and hash in the session.
  • Implement proper input validation and sanitization to prevent any potential injection attacks.
  • Implement strong access controls to restrict unauthorized access to sensitive resources.
  • Regularly update and patch the Play framework and its dependencies to ensure the latest security fixes are applied.

Compliant code

        import play.api.mvc._
import play.api.libs.json._
import org.mindrot.jbcrypt.BCrypt

object Application extends Controller {

  def login = Action(parse.json) { request =>
    val username = (request.body \\ "username").as[String]
    val password = (request.body \\ "password").as[String]

    val user = User.authenticate(username, password)

    if (user.isDefined) {
      Ok(Json.obj("status" -> "OK")).withNewSession.withSession("username" -> username)
    } else {
      Unauthorized(Json.obj("status" -> "KO", "message" -> "Invalid username or password"))
    }
  }
}

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

object User {
  def authenticate(username: String, password: String): Option[User] = {
    // Fetch the user from the database
    val user = Database.getUser(username)

    if (user.isDefined && BCrypt.checkpw(password, user.get.passwordHash)) {
      Some(user.get)
    } else {
      None
    }
  }
}
        
        

The updated code now uses the BCrypt password hashing function to securely store and verify user passwords. The password is hashed using BCrypt when it is stored in the database, and the BCrypt.checkpw function is used to verify the password when the user logs in. This function securely compares the password entered by the user with the hashed password stored in the database.

The withNewSession method is used to create a new session when the user logs in. This replaces the previous session and helps to prevent session fixation attacks. The username is stored in the session, but the password hash is not. This helps to prevent the password hash from being exposed if the session is compromised.

The code also now expects the password to be sent in the request body instead of the password hash. This means that the password hash is never exposed to the client, which helps to prevent pass the hash attacks.

The code also includes proper input validation and sanitization to prevent any potential injection attacks. The username and password are both extracted from the request body using the as[String] method, which ensures that they are both strings. This helps to prevent injection attacks by ensuring that the input is of the expected type.

The code also includes strong access controls to restrict unauthorized access to sensitive resources. The authenticate method checks that the user exists in the database and that the password entered by the user matches the hashed password stored in the database. If the authentication check fails, the method returns None, which results in an Unauthorized response being sent to the client.

References