The problem with allowing password reset is that there is a serious security hole in it.
Because in my file reset.php
it is sending the same account activation code that was generated when the user registered.
For an attacker, it would be easy to make several attempts, for example in this way:
example.com/login-system/reset.php?email=ponercualquieremail%key=generarcódigoaleatorio
And if it matches the table records users
, the attacker can change the password and gain access to the access system.
All that security hole due to the same activation code.
So, this link, to reset the password:
http://example.com/login-system/reset.php?email=example%40gmail.com&key=523db8c57a3d17d0860fa705c4c24ec62efc0c68f2f1443e39938361424099f1
It is the same to activate the account:
http://example.com/login-system/verify.php?email=example%40gmail.com&key=523db8c57a3d17d0860fa705c4c24ec62efc0c68f2f1443e39938361424099f1
What's more, I can save that code that I receive to activate the account, to reset the password without having to enter the email in the reset form password
, simply by changing it verify.php?
toreset.php?
Now I have the following structure of my tableusers
+----------+-----------+--------+----------+------------+--------+
| id_user | username | email | password | email_code | active |
+----------+-----------+--------------------------------+--------+
| 1 | karla | karla@ | $2y$10...| 23db8c5... | 1 |
+-------------+-------------+--------------+------------+---------
How can I correct this security problem, sending that activation code along with the user's id to another table and with an expiration time, and that when activating the account that record is deleted, and that when requesting to reset the password create a new verification code with expiration date and when resetting the password that record is deleted again.
My complete code.
register.php
session_start();
include "require.ini.php";
if (isset($_POST['formsubmitted'])) {
$msg = array();
if (empty($_POST['username'])) {
$msg[] = 'Por favor, ingrese un nombre de usuario';
} else {
$username = $_POST['username'];
}
if (empty($_POST['email'])) {
$msg[] = 'Por favor, ingrese su correo electrónico';
} else {
if (preg_match("/^([a-zA-Z0-9])+([a-zA-Z0-9\._-])*@([a-zA-Z0-9_-])+([a-zA-Z0-9\._-]+)+$/", $_POST['email'])) {
$email = $_POST['email'];
} else {
$msg[] = 'Tu dirección de correo electrónico no es válida';
}
}
if (strlen($_POST['password']) <6){
$msg[] = 'Su contraseña debe tener al menos 6 caracteres';
}
if ($_POST['password'] !== $_POST['password_again']){
$msg[] = 'Su contraseña no coincide';
} else {
$password = $_POST['password'];
}
if (empty($_POST['firstname'])) {
$msg[] = 'Por favor, ingrese su nombre';
} else {
$first_name = $_POST['firstname'];
}
if (empty($msg)) {
$stmt = $con->prepare("SELECT * FROM users WHERE email=? OR username=?");
$stmt->bind_param("ss",$email,$username);
$stmt->execute();
$stmt->store_result();
if ($stmt->num_rows>0) {
echo "¡El usuario con este correo electrónico ya existe!";
} else {
$hash_password = password_hash($password, CRYPT_BLOWFISH);
$key = bin2hex(openssl_random_pseudo_bytes(32));
//$key_two = bin2hex(random_bytes(32)); // Disponible apartir de PHP V.7
$active_default = 0;
$stmtA = $con->prepare("INSERT INTO users (username, email, password, first_name, email_code, active) VALUES (?, ?, ?, ?, ?, ?)");
$stmtA->bind_param("sssssi", $username,$email,$hash_password,$first_name,$key,$active_default);
if($stmtA->execute()){
echo 'El enlace de confirmación ha sido enviado por correo electrónico. ¡Por favor, haga clic en el enlace del mensaje para activar su cuenta!';
$to = $email;
$subject = "Por favor, verifique su cuenta.";
$message_body = 'Hola '.$first_name.',
¡Gracias por registrarte!
Estas aún solo paso de ser parte de nuestra comunidad.
Por favor, haga clic en este enlace para activar su cuenta:
http://example.com/login-system/verify.php?email='.urlencode($email).'&key='.$key.'';
mail($to, $subject, $message_body, 'From: [email protected]');
//header("location: index.php");
//exit;
} else {
echo "Ha ocurrido un error internamente, por favor, vuelva intertar enviar su solicitud más tarde";
}
}
} else {
foreach ($msg as $key => $values) {
echo ' <div>'.$values.'</div>';
}
}
}
reset.php
<?php
session_start();
include "require.php";
if (isset($_GET['email']) && preg_match('/^([a-zA-Z0-9])+([a-zA-Z0-9\._-])*@([a-zA-Z0-9_-])+([a-zA-Z0-9\._-]+)+$/', $_GET['email'])) {
$email = $_GET['email'];
}
if (isset($_GET['key']) && (strlen($_GET['key']) == 64)) {
$key = $_GET['key'];
}
if (isset($email) && isset($key)) {
//$email = $con->escape_string($_GET['email']);
//$key = $con->escape_string($_GET['key']);
$active_defaul = 1;
$stmt = $con->prepare("SELECT * FROM users WHERE email=? AND email_code=? AND active=?");
$stmt->bind_param("ssi",$email,$key,$active_defaul);
$stmt->execute();
$stmt->store_result();
//if ($result->num_rows == 0 )
if ($stmt->num_rows==0) {
//if ($stmt->num_rows>0) {
echo "¡Ingresó una URL inválida para restablecer la contraseña!";
} else {
echo '
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<form action="reset_password.php" method="post">
<label>New Password</label>
<input type="password" name="password" autocomplete="off"/>
<label>Confirm New Password</label>
<input type="password" name="password_again" autocomplete="off"/>
<input type="hidden" name="email" value="'.$email.'">
<input type="submit" name="form_reset" value="Guardar contraseña" />
</form>
</body>
</html>';
}
} else {
echo "¡Acceso denegado!";
}
?>
reset_password.php
session_start();
include "require.php";
if (isset($_POST['form_reset'])) {
$email = $_POST['email'];
$password = $_POST['password'];
$hash_password = password_hash($password, CRYPT_BLOWFISH);
$stmt = $con->prepare("UPDATE users SET password= ? WHERE email=? OR username=?");
$stmt->bind_param("sss", $hash_password,$email,$email);
if($stmt->execute()){
header("location: correcto.php");
} else {
header("location: error.php");
}
}