¿Cómo crear un sistema seguro de acceso con php & mysqli?

publicado por: Anonymous

Analices de acceso sistema seguro de diferentes plataforma Google Payoneer entre otras plataformas financieras.

En el siguiente analices encontré estas características:

  • Al primer intento fallido muestra el código captcha.
  • A los 5 intentos la cuenta del usuario es bloqueada por 15 minutos.
  • Al tener acceso existan o no los historial de fallo debe responder una pregunta de seguridad para continuar.
  • No permite un doble logueo.

Otras Referencias: crear script de inicio sesión segura

He logrado corregir mi antiguo código.

Nota: Edite mi pregunta para evitar que exista una posible pregunta duplicada.

Las correcciones:

La ip continuara bloqueada, no afectara en nada porque al estar bloqueada la ip solo se mostrara el código captcha.

He creado una nueva tabla fail_attempt con las siguientes columna: algo así mi idea.

id_fail_attempt   id_user   attempt   ip       datetime                 time
      1              1        5      ::1   2017-08-23 17:57:46   2017-08-23 18:12:46 

En la siguiente tabla login_attempts se registra los intentos fallidos de la ip

  id         ip        attempts        datetime               
  1         ::1           2       2017-08-23 17:57:46    

La tabla users

id   username   email   password   lastname  active
1     Hola      [email protected]    Hola        Hola      1

Cómo puedo insertar en la tabla fail_attempt el id del usuario, los intentos fallido, la ip el tiempo en el que el usuario estará bloqueado su acceso y como controlar el doble logueo del mismo usuario en el mismo sistema.

Código completo PHP

¿Cómo añado las nuevas funciones y crear un sistema seguro?

login.php

<?php
session_start();
$message="";
$captcha = true;

//
$con =  @new mysqli('localhost', 'root', '', 'systemuser');

  if(count($_POST)>0 && isset($_POST["vcode"]) && $_POST["vcode"]!=$_SESSION["vcode"]) {
    $captcha = false;
    $message = "Los caracteres escritos no coinciden con la palabra de verificación. Inténtalo de nuevo.";
  }

  $ip = $_SERVER['REMOTE_ADDR'];

  //Bloqueamos la ip por un día
  $result = mysqli_query($con,"SELECT * FROM failed_login WHERE ip='$ip' AND date BETWEEN DATE_SUB( NOW() , INTERVAL 1 DAY ) AND NOW()");
  $row  = mysqli_fetch_assoc($result);
  //Obtenemos datos para comprar intentos y para resetear intentos por su ultimo fecha.
  $failed_login_attempt = mysqli_real_escape_string($con,$row['attempts']);
  //Liberamos memoria.
  mysqli_free_result($result);

  if(count($_POST)>0 && $captcha == true) {
    $username = mysqli_real_escape_string($con, $_POST["username"]);
    $password = mysqli_real_escape_string($con, $_POST["password"]);
    $username = htmlentities($username);
    $password = htmlentities($password);
    $save_passw = sha1($password);
    $sql = "SELECT * fROM users where username='$username' AND password='$save_passw' AND active='1' ";
    $query = mysqli_query($con, $sql);

    $rowU  = mysqli_fetch_assoc($query);
    $UsernamaDB = mysqli_real_escape_string($con, $rowU["username"]);
    $passwordDB = mysqli_real_escape_string($con, $rowU["password"]);

    if($failed_login_attempt <1) {
            //Si es su primer intento fallido, incluimos el primer registro en la BD
            $con->query("INSERT INTO failed_login (ip,attempts,date) VALUES ('$ip', 1, NOW())");
            } else {
                if($failed_login_attempt <2){
                //En caso de ya estar en la BD, sacamos el valor y agregamos +1
                $contador = $row['attempts'] + 1;
                $con->query("UPDATE failed_login SET attempts='$contador', date=NOW() WHERE ip = '$ip'");
            }
        }


        if (empty($_POST) === false) {
            $username = $_POST['username']; $password = $_POST['password'];
            if (empty($username) === true || empty($password) === true) {
                $message = "Es necesario introducir un nombre de usuario y contraseña";
             } elseif ($username != $UsernamaDB) {
                $message = "El 'Usuario' que has introducido no coincide. ";
             } elseif ($save_passw != $passwordDB) {
                $message = "Tu 'Contraseña' introducido no coincide. ";
             } elseif($save_passw == $passwordDB && $username == $UsernamaDB) {
                $_SESSION["id_user"] = 1;
                $con->query("DELETE FROM login_attempts WHERE ip = '$ip'");
             }
            }
        }
        if(isset($_SESSION["id_user"])) {
            header("Location:http://localhost/index.php");
        }
?>
<h1><?php if($message!="") { echo $message; } ?></h1>
<form name="frmUser" method="post" action="">
  <input type="text" name="username" placeholder="Usuario">
  <input type="password" name="password" placeholder="Contraseña">
  <!-- captcha-->
  <?php if (isset($failed_login_attempt) && $failed_login_attempt >= 1) { ?>
    <br><img src="image.php" id="phoca-captcha"/>
  <input name="vcode" type="text" placeholder="Codigo captcha">
   <?php } ?>
  <!-- fin-->
  <input type="submit" value="Iniciar sesión" id="button-login">
</form> 

solución

Desde mi punto de vista ante todo debes proteger su Base de datos ante posibles ataques de inyección, piensa, para que sirve crear un sistema de login avanzado si después uno podría modificar fácilmente su Base de datos. Para ello podrías usar mysqli::prepare o PDO.

Si quieres saber más como evitar la inyección SQL, te dejo esta pregunta con grandes respuestas en SOes.

En mi ejemplo utilizo mysqli::prepare, el principal y más esencial beneficio de las declaraciones preparadas es la eliminación de todos los peligros del formato manual.

Otro factor importante es el almacenaje de contraseñas seguras. En mi ejemplo utilizo password_verify, para comprobar si la contraseña conciden, importante, cuando registras un nuevo usuario utiliza la función password_hash() para crear un hash de contraseña seguro (No utilice sha1 o md5, ya que son vulnerables).

Mas información sobre Almacenamiento de contraseñas PHP y MYSQL
en SOes.

Ejemplo crear un hash de contraseña:

$contrasena = password_hash($_POST['contrasena'], PASSWORD_BCRYPT);

BCRYPT, siempre tendrá 60 caracteres.

Ejemplo completo:

Voy a poner un posible ejemplo a tus deseos.

Desde mi punto de vista al primer intento mostrar el código captcha, podría ser incomodo, ya que un usuario se equivoca fácilmente en el primer intento, vamos a poner en el 3 intento, aunque este valor se podría modificar como uno desea en realidad.

Compuse el sistema basándome en los siguientes puntos (probado en localhost):

  • 3 intento, mostramos el código captcha y incrementamos.
  • 5 intento desactivamos el usuario durante 15min.
  • Intento 8 fallido en login_Attempts, se bloquea el login durante 1 día por IP.
  • En el intento 10, inactivamos el usuario hasta contactar con el
    administrador.
  • En caso introducir datos no validos inserta un intento fallido mediante su IP.
  • Inicia sesión mediante usuario y correo electrónico.
  • Seguridad en la Base de datos mediante sentencias preparadas.
  • password_hash, un hash de contraseña usando un algoritmo de hash
    fuerte de único sentido.

Lo del doble logueo, personalmente no lo veo cómodo para los usuarios, siempre hay que mirar la comodidad del usuario y en ocasiones depende la aplicación es más cómodo trabajar con dos sesiones abiertas en diferentes ordenadores, piensa que si uno realmente quiere podría también fácilmente, grabar la pantalla de su aplicación y dárselo a terceros XD.

Base de datos

CREATE TABLE `fail_attempt` (
  `id_fail_attempt` int(11) UNSIGNED NOT NULL auto_increment primary key,
  `id_user` int(11) NOT NULL,
  `attempt` smallint(2) NOT NULL,
  `ip` varchar(100) COLLATE utf8_spanish_ci NOT NULL,
  `datetime` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_spanish_ci;

CREATE TABLE `login_attempts` (
  `id` int(11) UNSIGNED NOT NULL auto_increment primary key,
  `ip` varchar(100) NOT NULL,
  `attempts` smallint(2) NOT NULL,  
  `datetime` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_spanish_ci;

CREATE TABLE `users` (
  `id` int(11) UNSIGNED NOT NULL auto_increment primary key,
  `username` varchar(60) NOT NULL unique,
  `email` varchar(200) NOT NULL unique,
  `password` varchar(60) NOT NULL,
  `lastname` varchar(255) NOT NULL,
  `active` smallint(1) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_spanish_ci;

INSERT INTO `users` (`id`, `username`, `email`, `password`, `lastname`, `active`) VALUES
(1, 'Daniel', '[email protected]', '$2y$10$IptiWEQlWuIZHPvl7wVXfele2nKi1ics9EuTzeHv29MT7KUZfYTGa', 'Bulten', 1);

Nota: Para hacer pruebas la contraseña con hash $2y$10$IptiWEQlWuIZHPvl7wVXfele2nKi1ics9EuTzeHv29MT7KUZfYTGa equivale a 123 y deberás modificar el correo electrónico al suyo si uno quiere.

index.php

    <?php
      //Arrancamos sesión.
      session_start();
      //Comprobamos si esta iniciado sesión, caso contrario redirigimos al login.
      if (!isset($_SESSION['usuario'])) { header('Location:login.php'); }
    ?>
    <!DOCTYPE html>
    <html lang="es-ES">
    <body>
        Bienvenido <?php echo $_SESSION['usuario']; ?>  
        <a href="logout.php">Cerrar sesión</a>
    </body>
    </html>

conexion.php (Estilo orientado a objetos).

    <?php
        //Configuración.
        $ServerName = "Localhost";
        $Username = "root";
        $PassWord = "root";
        $DataBase = "login_stackoverflow";    

        //Creamos conexión.
        $con = new mysqli($ServerName, $Username, $PassWord, $DataBase);

        //Comprobamos conexión.
        if ($con->connect_error) {
            exit("La conexión fallo: " . $con->connect_error);
        }

        //Caracteres UTF-8 para MySQL.
        if (!$con->set_charset("utf8")) {
            printf("Error cargando el conjunto de caracteres utf8: %sn", $con->error);
            exit();
        }
    ?>

login.php

En mi ejemplo he usado en vede usuario el correo electrónico para iniciar sesión, ya que es unique como un usuario, aunque este valor es fácilmente modificable por usuario si uno desea.

    <?php
    //Arrancamos sesión.
    session_start();

    //Caso estar iniciado sesión, redirigimos automatico al index.
    if (isset($_SESSION['usuario'])) { header('Location:index.php'); }
    //Conexión -> SQL
    require_once'conect.php';
    //Calculamos intentos fallidos por IP.
    $stmtIP = $con->prepare("SELECT attempts,datetime FROM login_attempts WHERE ip=?");
    //Ligar parametros marcadores.
    $stmtIP->bind_param("s",$addres);   
    //Obtenemos IP.
    $addres = $_SERVER['REMOTE_ADDR'];//Ip          
    //Ejecutar sentencia.
    $stmtIP->execute();             
    //Ligar variables de resultados BD.
    $stmtIP->bind_result($totalattempt,$getdatetime);
    //Obtenemos valores.
    $stmtIP->fetch();   
    //Cerrar sentencia.
    $stmtIP->close();   
    //Si supera más de 10 intentos, el IP quedara inactivo durante 1 dia.
    if ($totalattempt===8) {
        if (!isset($_SESSION['inactivo1day'])) {
            $_SESSION['inactivo1day'] = true;
        }
        //Reseteo intentos a 0 pasado 1 dia.
        if(strtotime($getdatetime) <= strtotime("-1 day")) { // -15 minutes podria ser -30minutes, -1hours, -day etc...
            //Sentencia.
            $stmtDel = $con->prepare("DELETE FROM login_attempts WHERE ip=?");
            //Ligar parametros marcadores.
            $stmtDel->bind_param("s",$addres);          
            $stmtDel->execute();
            //Cerrar sentencia.
            $stmtDel->close();
            //Eliminamos session inactivo
            unset($_SESSION['inactivo1day']);           
            //Redirigimos a login
            echo '<script>window.location="login.php"</script>';
        } else {
            //Mensaje cuenta desactivado durante 15min.
            $msg = "La IP ha quedado desactivado desde las {$getdatetime}, durante 1 dia., intentelo más tarde.";
        }           
    }
?>
<!DOCTYPE html>
<html lang="es-ES">
<head>  
    <!-- Libreria jQuery. -->       
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>  

    <meta charset="utf-8" /><!-- Character encoding. -->
    <script>
        $(document).ready(function() {
            //Formulario login.
            $(document).on('submit', '#login-form', function() {  
                //Data formulario    
                var data = $(this).serialize();                 
                $.ajax({        
                    type : 'POST',
                    url  : 'login_control.php',
                    data : data,
                    success :  function(data) {            
                        $("#result-login").html(data);  
                    }
                });             
                return false;    
            });         
        });
    </script>
</head>
<body>  
    <?php   
    if (!isset($_SESSION['inactivo1day'])) {            
        //En caso estar activado la sesión del captcha, se ativa el formulario captcha.         
    ?>      
            <h2>Por favor, introduce tu usuario / correo electrónico y contraseña para comenzar.</h2>               
            <form method="POST" id="login-form">                            
                <input type="text" name="usuario" placeholder="Usuario / Correo electrónico *" />
                <input type="password" name="password" placeholder="Contrase&ntilde;a *" /> 
                <?php
                if (isset($_SESSION['captchaValidar'])) {
                ?>
                <h2>Verifica los caracteres del 'Captcha' para continuar</h2>
                <img src='captcha_code.php' />              
                <input name='captcha_code' type='text' placeholder='Codigo captcha' />  
                <?php
                }
                ?>


                <button type="submit">Inicia sesión</button>                    
            </form>
            <br />
            <div id="result-login">
            <!-- Respuesta AJAX -->
                <?php if (isset($_SESSION['captchaValidar'])) {
                    echo $_SESSION['captchaValidar'];
                } ?>
            </div>      
        <?php

    } else {
        echo $msg;
    }
    ?>      
</body>
</html>

login_control.php

<?php
//Arrancamos sesión.
session_start();
//Zona region. 
date_default_timezone_set("Europe/Madrid");

//Reseteo de variables.
$msg = $usuario = $password = $captcha_code = NULL;

//Definido formulario y no es NULL.
if (isset($_POST) && !isset($_SESSION['captchaValidar'])) {

    //Inputs vacios.
    if (empty($_POST['usuario'])) {
        $msg = "<p style='background-color:red; color:white; padding:1rem; margin-top:1rem; max-width:300px;'>El usuario / correo electrónico es obligatorio.";
    } elseif (empty($_POST['password'])) {
        $msg = "<p style='background-color:red; color:white; padding:1rem; margin-top:1rem; max-width:300px;'>La contraseña es obligatorio.";
    } else {
        //Obtenemos datos inputs.
        $usuario = $_POST['usuario'] ?: '';
        $password = $_POST['password'] ?: '';
    }

    //Datos son verdadero.
    if ($usuario && $password) {        
        //Conexión -> SQL
        require_once'conect.php';
        //Obtenemos IP.
        $addres = $_SERVER['REMOTE_ADDR'];//Ip      
        //Comprobamos si existe usuario en la BD.
        //Sentencia.
        $stmt = $con->prepare("SELECT id,username,password FROM users WHERE username=? OR email=? LIMIT 1");
        //Ligar parametros marcadores, es decir, el valor ?, es decir email='$usuario' y las "s", estamos diciendo que $usuario es un string (cadena), si existen más, es importante su orden.
        $stmt->bind_param("ss",$usuario,$usuario);
        //Ejecutar sentencia.
        $stmt->execute();
        //Devuelve el número de filas de un conjunto de resultados de una sentencia.
        $stmt->store_result();
        //Comprobamos si existen registros
        if ($stmt->num_rows===1) {
            //Ligamos los datos desde la BD.
            $stmt->bind_result($id_user,$usernameBD,$passwordHASH);
            if ($stmt->fetch()) {

                //Comprobamos si el usuario esta desactivado  o tiene algun intento fallido, utilizamos el 'ID' del usuario.
                $stmtA = $con->prepare("SELECT attempt,datetime FROM fail_attempt WHERE id_user=? LIMIT 1");
                //Ligar parametros marcadores.
                $stmtA->bind_param("i",$id_user);               
                //Ejecutar sentencia.
                $stmtA->execute();
                //Devuelve el número de filas de un conjunto de resultados de una sentencia.
                $stmtA->store_result();
                //Con esta variable sabemos si necesitamos un insert o un update, ya que si encuentra  0 registros es insert y si es 1 va ser un update.
                $check_attempt = $stmtA->num_rows;
                //Ligar variables de resultados BD.
                $stmtA->bind_result($attempts,$getdatetime);
                //Obtenemos valores.
                $stmtA->fetch();    
                //Cerrar sentencia.
                $stmtA->close();

                //Mientras los intentos no supera a 11, continuamos con el login.
                if ($attempts < 11) {
                    //Si existen 5 intentos fallidos, no continua con el login, sino esta en modo desactivado durante x tiempo.
                    if ($attempts==5) {
                        //Reseteo intentos a 6 pasado 15min., para continuar y evitar en este bucle.
                        if(strtotime($getdatetime) <= strtotime("-1 minutes")) { // -15 minutes podria ser -30minutes, -1hours, -dia etc...
                            //Sentencia.
                            $stmtUp = $con->prepare("UPDATE fail_attempt SET attempt=?, ip=?  WHERE id_user=? LIMIT 1");
                            //Ligar parametros marcadores (???, es decir, $attempt='$int' and $ip='$addres' etc...).
                            $stmtUp->bind_param("isi", $int,$addres,$id_user);
                            //valores parametros marcadores.        
                            $int = 6;//Inilizamos en 6 intentos.
                            //Ejecutar sentencia.
                            $stmtUp->execute();
                            //Cerrar sentencia.
                            $stmtUp->close();
                            //Eliminamos session inactivo
                            unset($_SESSION['inactivo15']);
                            //Alerta.
                            echo '<script>window.alert("Pasaron 15min., su cuenta ha sido activado");</script>';
                            //Redirigimos a acceder
                            echo '<script>window.location="login.php"</script>';
                        } else {
                            //Mensaje cuenta desactivado durante 15min.
                            $msg = "Tu cuenta ha quedado desactivado desde las {$_SESSION['inactivo15']}, durante 15min., intentelo más tarde.";
                        }           
                    } else {
                        //En caso contrario continuamos con el login.

                        //Comprobamos password mediante la función password_verify.
                        if (password_verify($password, $passwordHASH)) {//Correcto, creamos la sesión del usuario.                                      
                            //Reseteamos intentos fallidos.
                            if ($check_attempt===1) {
                                //Reseteamos intentos en 0 para fail_attempt.
                                //Sentencia.
                                $stmtUp = $con->prepare("UPDATE fail_attempt SET attempt=?, ip=?, datetime=? WHERE id_user=? LIMIT 1");
                                //Ligar parametros marcadores.
                                $stmtUp->bind_param("issi",$reset_attempt,$addres,$datetime,$id_user);
                                //Creo valores marcadres.
                                $reset_attempt = 0;
                                //id_user ya lo hemos obtenido en la comprobacion si existe el usuario.
                                //Adrres ya es obtenido anteriormente.                      
                                $datetime = date('Y-m-d  H:i:s', time());
                                //Ejecutar sentencia.
                                $stmtUp->execute();
                                //Cerrar sentencia.
                                $stmtUp->close();

                                //Eleminas registro de login_attempts
                                //Sentencia.
                                $stmtDel = $con->prepare("DELETE FROM login_attempts WHERE ip=?");
                                //Ligar parametros marcadores.
                                $stmtDel->bind_param("s",$addres);          
                                $stmtDel->execute();
                                //Cerrar sentencia.
                                $stmtDel->close();
                            }
                            //Creamos sesión al usuario.
                            $_SESSION['usuario'] = $usernameBD;         
                            //Redirigimos a pagina protegida
                            echo '<script>window.location="index.php"</script>';

                        } else { //Contraseña incorrecto, añadimos intentos fallidos.                                   

                            //Actualizamos intentos usuario.
                            if ($check_attempt===0) {                       
                                //Insertar primer intento fallido.

                                //Sentencia.
                                $stmtIn = $con->prepare("INSERT INTO fail_attempt (id_user,attempt,ip,datetime) VALUES (?,?,?,?)");
                                //Ligar parametros marcadores.
                                $stmtIn->bind_param("iiss",$id_user,$attempts,$addres,$datetime);
                                //Creo valores marcadres.
                                //id_user ya lo hemos obtenido en la comprobacion si existe el usuario.
                                //Adrres ya es obtenido anteriormente.
                                $attempts = 1;
                                $datetime = date('Y-m-d  H:i:s', time());
                                //Ejecutar sentencia.
                                $stmtIn->execute();
                                //Cerrar sentencia.
                                $stmtIn->close();
                            } else {
                                //Actualizar, es decir, incrementar intento fallido.
                                //Sentencia.
                                $stmtUp = $con->prepare("UPDATE fail_attempt SET attempt=attempt+1, ip=?, datetime=? WHERE id_user=?");
                                //Ligar parametros marcadores.
                                $stmtUp->bind_param("ssi",$addres,$datetime,$id_user);
                                //Creo valores marcadres.
                                //id_user ya lo hemos obtenido en la comprobacion si existe el usuario.
                                //Adrres ya es obtenido anteriormente.                      
                                $datetime = date('Y-m-d  H:i:s', time());
                                //Ejecutar sentencia.
                                $stmtUp->execute();
                                //Cerrar sentencia.
                                $stmtUp->close();

                                //A no actualizar el navegador, incremento manualmente el attemp
                                $attempts = $attempts +1;       

                            }

                            //Comprobamos si ha excedido más de 8 intentos fallidos mediante su IP.                         
                            //Sentencia
                            $stmtIP = $con->prepare("SELECT attempts FROM login_attempts WHERE ip=?");
                            //Ligar parametros marcadores.
                            $stmtIP->bind_param("s",$addres);                               
                            //Ejecutar sentencia.
                            $stmtIP->execute();
                            //Devuelve el número de filas de un conjunto de resultados de una sentencia.
                            $stmtIP->store_result();
                            //Comprobamos si existen algun registro, para asi diferenciar si hacer un insert o update.  
                            $check_ip_attempts = $stmtIP->num_rows;         
                            //Ligar variables de resultados BD.
                            $stmtIP->bind_result($ip_attempts);
                            //Obtenemos valores.
                            $stmtIP->fetch();   
                            //Cerrar sentencia.
                            $stmtIP->close();               


                            //Insertar fallo a tabla login_attempts mediante su IP.
                            if ($check_ip_attempts===0) { 

                                //Sentencia.
                                $stmtIn = $con->prepare("INSERT INTO login_attempts (ip,attempts,datetime) VALUES (?,?,?)");
                                //Ligar parametros marcadores.
                                $stmtIn->bind_param("sis",$addres,$int,$datetime);
                                //Creo valores marcadres.                                   
                                //Adrres ya es obtenido anteriormente.                                  
                                $int = 1;
                                $datetime = date('Y-m-d  H:i:s', time());
                                //Ejecutar sentencia.
                                $stmtIn->execute();
                                //Cerrar sentencia.
                                $stmtIn->close();
                            } else { //Update fallo IP.
                                //Sentencia.
                                $stmtUp = $con->prepare("UPDATE login_attempts SET ip=?, attempts=attempts+1, datetime=? WHERE ip=?");
                                //Ligar parametros marcadores.
                                $stmtUp->bind_param("sss",$addres,$datetime,$addres);
                                //Creo valores marcadres.                               
                                //Adrres ya es obtenido anteriormente.                      
                                $datetime = date('Y-m-d  H:i:s', time());
                                //Ejecutar sentencia.
                                $stmtUp->execute();
                                //Cerrar sentencia.
                                $stmtUp->close();
                                //Incrementamos manualmente, a no actualizar navegador.
                                $ip_attempts = $ip_attempts+1;
                            }

                            //IP esta bloqueda.
                            if ($ip_attempts===8) {     
                                $_SESSION['inactivo1day'] = true;
                                //Redirigimos a acceder
                                echo '<script>window.location="login.php"</script>';        
                            }

                            //Mensajes contraseña fallidos.

                            //Primer fallo, mostramos el captcha.
                            if ($attempts<2) {
                                $msg = "<p style='background-color:red; color:white; padding:1rem; margin-top:1rem; max-width:300px;'>Tu contraseña es incorrecto, vuelva a intentarlo.";   
                            } elseif ($attempts ==3) {                              
                                //Arrancamos sesión del captcha.
                                $_SESSION['captchaValidar'] = "<p style='background-color:red; color:white; padding:1rem; margin-top:1rem; max-width:300px;'>Tu contraseña es incorrecto, vuelva a intentarlo.<p>";
                                echo '<script>window.location="login.php"</script>';                            
                            } elseif ($attempts==4) {
                                $msg = "<p style='background-color:red; color:white; padding:1rem; margin-top:1rem; max-width:300px;'>Tu contraseña es incorrecto, te queda un intento más para desactivar su cuenta durante 15min.</p>";
                            } elseif ($attempts==5) {
                                $_SESSION['inactivo15'] = date("H:i:s");
                                $msg = "<p style='background-color:red; color:white; padding:1rem; margin-top:1rem; max-width:300px;'>Tu cuenta ha quedado desactivada desde las {$_SESSION['inactivo15']}, durante 15min., intentelo más tarde.</p>";
                            } elseif ($attempts <=9) {
                                $msg = "<p style='background-color:red; color:white; padding:1rem; margin-top:1rem; max-width:300px;'>Tu contraseña es incorrecto, vuelva a intentarlo.<p>";
                            } elseif ($attempts<=10) {
                                $msg = "<p style='background-color:red; color:white; padding:1rem; margin-top:1rem; max-width:300px;'>Tu contraseña es incorrecta, llevas 9 intentos fallidos, te queda un intento más, sino deberás ponerte en contacto con administración.</p>";
                            } else {
                                $msg = "<p style='background-color:red; color:white; padding:1rem; margin-top:1rem; max-width:300px;'>Tu cuenta ha quedado desactivada, por favor ponte en contacto con [email protected] para activar su cuenta de nuevo.</p>";
                            }                           
                        }
                    }                   
                } else {
                //Supera el maximo de intentos permitido, cuenta esta inactivo durante X tiempo.
                    $msg = "<p style='background-color:red; color:white; padding:1rem; margin-top:1rem; max-width:300px;'>Tu cuenta ha quedado desactivada, por favor ponte en contacto con [email protected] para activar su cuenta de nuevo.";                          
                }
            } $stmt->close();
        } else {
            //Cerrar sentencia.
            $stmt->close(); 

            //Comprobamos si ha excedido más de 8 intentos fallidos mediante su IP.                         
            //Sentencia
            $stmtIP = $con->prepare("SELECT attempts FROM login_attempts WHERE ip=?");
            //Ligar parametros marcadores.
            $stmtIP->bind_param("s",$addres);                               
            //Ejecutar sentencia.
            $stmtIP->execute();
            //Devuelve el número de filas de un conjunto de resultados de una sentencia.
            $stmtIP->store_result();
            //Comprobamos si existen algun registro, para asi diferenciar si hacer un insert o update.  
            $check_ip_attempts = $stmtIP->num_rows;         
            //Ligar variables de resultados BD.
            $stmtIP->bind_result($ip_attempts);
            //Obtenemos valores.
            $stmtIP->fetch();   
            //Cerrar sentencia.
            $stmtIP->close();


            //IP esta bloqueda.
            if ($ip_attempts===8) {     
                $_SESSION['inactivo1day'] = true;
                //Redirigimos a acceder
                echo '<script>window.location="login.php"</script>';        
            }

            if ($check_ip_attempts===0) { //Insert fallo IP.

                //Sentencia.
                $stmtIn = $con->prepare("INSERT INTO login_attempts (ip,attempts,datetime) VALUES (?,?,?)");
                //Ligar parametros marcadores.
                $stmtIn->bind_param("sis",$addres,$int,$datetime);
                //Creo valores marcadres.                                   
                //Adrres ya es obtenido anteriormente.                                  
                $int = 1;
                $datetime = date('Y-m-d  H:i:s', time());
                //Ejecutar sentencia.
                $stmtIn->execute();
                //Cerrar sentencia.
                $stmtIn->close();
            } else { //Update fallo IP.
                //Sentencia.
                $stmtUp = $con->prepare("UPDATE login_attempts SET ip=?, attempts=attempts+1, datetime=? WHERE ip=?");
                //Ligar parametros marcadores.
                $stmtUp->bind_param("sss",$addres,$datetime,$addres);
                //Creo valores marcadres.                               
                //Adrres ya es obtenido anteriormente.                      
                $datetime = date('Y-m-d  H:i:s', time());
                //Ejecutar sentencia.
                $stmtUp->execute();
                //Cerrar sentencia.
                $stmtUp->close();
            }           

            //Arrancamos sesión del captcha.
            $_SESSION['captchaValidar'] = "<p style='background-color:red; color:white; padding:1rem; margin-top:1rem; max-width:300px;'>El usuario / correo electrónico no existen.<p>";
            echo '<script>window.location="login.php"</script>';
        }
    }
} else {
    //Comprobamos que esten escrito correctamente los caracteres del captcha.
    if ($_POST["captcha_code"]!=$_SESSION["captcha_code"]) {                            
        $msg = "<div class='dangerB font1'>Los caracteres escritos no coinciden con la palabra de verificación. Inténtalo de nuevo.</div>";
    } else { //Caracteres coinciden.
        //Destruimos la sesión.             
        unset($_SESSION['captchaValidar']);
        //Redirigimos al login.
        echo '<script>window.location="login.php"</script>';
    }   
}
//Respuesta Ajax
echo $msg;
?>

captcha_code.php

Nota: deberas crear dos carpetas font/SpecialElite.ttf y img/captcha.png, dicha imagen o fuente pueden ser personalizados a gusto de cada uno.

<?php
session_start();
$random_alpha = md5(rand());
$captcha_code = substr($random_alpha, 0, 6); // Numero total de caracters en el captcha

$_SESSION["captcha_code"] = $captcha_code;
$target_layer = imagecreatefrompng("img/captcha.png"); /// Fondo personalizado para el sistema de captcha.
$textColor = imagecolorallocate($target_layer, 71,171,118); // Color de la fuente
$lineColor = imagecolorallocate($target_layer, 255,255,255); // Color de las lineas del captcha
$imageInfo = getimagesize("img/captcha.png"); // fondo de la imagen

$linesToDraw = 40; // numero total de lineas en el captcha

for( $i = 10; $i < $linesToDraw; $i++ )  {      
    $xStart = mt_rand( 0, $imageInfo[ 0 ] );
    $xEnd = mt_rand( 0, $imageInfo[ 0 ] );    
    imageline($target_layer, $xStart, 0, $xEnd, $imageInfo[1], $lineColor );    
}

imagettftext($target_layer, 35, 2, 41, 50, $textColor, "font/SpecialElite.ttf", $captcha_code );

header("Content-type: image/png");
imagepng($target_layer);
imagedestroy($target_layer); 
?>

logout.php

<?php
    //Activar sesión.
    session_start();
    //Remover sesión.
    session_unset();
    //Destruir sesión.
    session_destroy();
    //Redirigimos
    header('location: login.php');
?>

Estructura para hacer pruebas facilmente en localhost:

RAIZ
font / SpecialElite.ttf (font)
img / captcha.png (dimensiones: 250×60 pixeles)
captcha_code.php
conect.php
index.php
login.php
login_control.php
logout.php

Respondido por: Anonymous

Leave a Reply

Your email address will not be published. Required fields are marked *