Enhancement of password security
import play.api._
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)
)
def login = Action { implicit request =>
val user = userForm.bindFromRequest.get
val dbUser = UserDAO.find(user.username)
if(dbUser.isDefined && dbUser.get.password == user.password) {
Redirect(routes.Application.index()).withSession("username" -> user.username)
} else {
Unauthorized("Wrong username or password")
}
}
In the above vulnerable code, we have a
User
case class with
username
and
password
fields. The form data is directly mapped to this case class without any sort of encryption or hashing. This means that the password is stored in plain text in the database, which is a security risk.
The
login
function retrieves the user from the database using the provided username and then directly compares the provided password with the one stored in the database. If they match, the user is logged in. If not, an Unauthorized error is returned.
The problem here is that the passwords are stored in plain text in the database. This means that if an attacker were to gain access to the database, they would be able to see all the passwords. Also, since the passwords are not hashed or encrypted, it would be easy for an attacker to crack weak passwords using a brute force attack.
The correct way to handle passwords is to never store them in plain text. Instead, passwords should be hashed using a strong cryptographic hash function and the hash should be stored in the database. When a user logs in, the provided password is hashed and the hash is compared with the one stored in the database. This way, even if an attacker gains access to the database, they would only see the hashes and not the actual passwords.
import play.api._
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)
)
def login = Action { implicit request =>
val user = userForm.bindFromRequest.get
val dbUser = UserDAO.find(user.username)
if(dbUser.isDefined && BCrypt.checkpw(user.password, dbUser.get.password)) {
Redirect(routes.Application.index()).withSession("username" -> user.username)
} else {
Unauthorized("Wrong username or password")
}
}
def register = Action { implicit request =>
val user = userForm.bindFromRequest.get
val hashedPassword = BCrypt.hashpw(user.password, BCrypt.gensalt())
val newUser = User(user.username, hashedPassword)
UserDAO.create(newUser)
Ok("User created")
}
The updated code now uses the BCrypt hashing algorithm to store passwords in the database. BCrypt is a strong, slow hashing algorithm that is resistant to brute-force and rainbow table attacks.
In the
register
method, the user's plaintext password is hashed using BCrypt and a salt (generated by
BCrypt.gensalt()
) before it is stored in the database. This ensures that even if the database is compromised, the attacker will not have access to the plaintext passwords.
In the
login
method, the user's plaintext password is hashed and compared to the hashed password stored in the database using
BCrypt.checkpw()
. This method securely compares the hashes to prevent timing attacks.
This code also needs to be complemented with password complexity requirements on the client side to prevent weak passwords from being used. Additionally, account lockout mechanisms should be implemented to prevent brute-force attacks.
Regular updates and patches to the application and its dependencies are also necessary to address any security vulnerabilities. Users should also be educated about the importance of using strong and unique passwords.