Secure password reset functionality
import play.api.mvc._
import play.api.data._
import play.api.data.Forms._
case class PasswordResetForm(email: String)
val passwordResetForm = Form(
mapping(
"email" -> email
)(PasswordResetForm.apply)(PasswordResetForm.unapply)
)
def sendPasswordResetLink = Action { implicit request =>
passwordResetForm.bindFromRequest.fold(
formWithErrors => {
// handle errors
},
passwordResetForm => {
val resetLink = routes.Application.resetPassword(passwordResetForm.email).absoluteURL()
EmailService.send(passwordResetForm.email, resetLink)
Ok("Password reset link has been sent to your email.")
}
)
}
def resetPassword(email: String) = Action {
Ok(views.html.resetPassword(email))
}
The above Scala code uses the Play framework to handle password reset requests. The
sendPasswordResetLink
function is the endpoint that is used to initiate the password reset process. It takes a form submission with an email address, generates a password reset link using the
absoluteURL
function, and sends the link to the user's email address.
The vulnerability lies in the
absoluteURL
function. This function generates an absolute URL based on the current request's host header. However, an attacker can manipulate the host header to point to a domain under their control. When the
absoluteURL
function generates the password reset link, it will use the malicious host, resulting in a password reset link that points to the attacker's domain.
If the user clicks on the link and enters their new password, the attacker can capture this information and gain unauthorized access to the user's account. This type of attack is known as password reset poisoning.
import play.api.mvc._
import play.api.data._
import play.api.data.Forms._
import java.util.UUID
case class PasswordResetForm(email: String)
val passwordResetForm = Form(
mapping(
"email" -> email.verifying("Invalid email address", e => e.contains("@"))
)(PasswordResetForm.apply)(PasswordResetForm.unapply)
)
def sendPasswordResetLink = Action { implicit request =>
passwordResetForm.bindFromRequest.fold(
formWithErrors => {
// handle errors
},
passwordResetForm => {
val token = UUID.randomUUID().toString
val resetLink = routes.Application.resetPassword(token).absoluteURL(secure = true)
EmailService.send(passwordResetForm.email, resetLink)
Ok("Password reset link has been sent to your email.")
}
)
}
def resetPassword(token: String) = Action {
// Verify the token before resetting the password
Ok(views.html.resetPassword(token))
}
The updated code includes several changes to address the password reset poisoning vulnerability:
1. Email Validation: The email address is validated to ensure it is in the correct format. This is done using the
verifying
method on the email field in the form mapping.
2. Secure Reset Link Generation: A secure, random token is generated using
UUID.randomUUID().toString
. This token is used as the parameter for the
resetPassword
route instead of the email address. This prevents an attacker from manipulating the email parameter in the reset link.
3. Secure Communication: The
absoluteURL
method is called with the
secure
parameter set to
true
. This ensures that the reset link is generated with the HTTPS protocol, ensuring the confidentiality and integrity of the reset process.
4. Token Verification: In the
resetPassword
action, the provided token should be verified before allowing the password to be reset. This could involve checking the token against a list of valid tokens stored in the database, for example.
These changes help to ensure that the password reset process is secure and not susceptible to password reset poisoning attacks.