Secure system access analyzes of different Google Payoneer platforms among other financial platforms.
In the following analyzes I found these characteristics:
- On the first failed attempt it shows the captcha code.
- After 5 attempts the user's account is blocked for 15 minutes.
- When you have access whether or not the fault history exists, you must answer a security question to continue.
- Does not allow double logging.
Other References: create secure login script
I have managed to fix my old code.
Note: Please edit my question to avoid possible duplicate question.
The corrections:
It ip
will continue to be blocked, it will not affect anything because being blocked ip
will only show the captcha code.
I have created a new fail_attempt table with the following columns: something like my 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
The following table login_attempts
lists the failed attempts of theip
id ip attempts datetime
1 ::1 2 2017-08-23 17:57:46
The boardusers
id username email password lastname active
1 Hola hola@ Hola Hola 1
How can I insert in the table fail_attempt
the id
user, the failed attempts, the ip
time in which the user will be blocked access and how to control the double login of the same user in the same system.
Full PHP code
How do I add new features and create a secure system?
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>
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
omd5
, ya que son vulnerables).Mas información sobre Almacenamiento de contraseñas PHP y MYSQL en SOes.
Ejemplo crear un hash de contraseña:
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):
login_Attempts
, se bloquea el login durante 1 día por IP.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
index.php
conexion.php (Estilo orientado a objetos).
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.
login_control.php
captcha_code.php
logout.php
RAIZ
font / SpecialElite.ttf (font)
img / captcha.png (dimensiones: 250x60 pixeles)
captcha_code.php
conect.php
index.php
login.php
login_control.php
logout.php
What you should do is
login_attempts
, change the attributeip
touser_id
orusername
(in the case that it is not repeated) to establish the relationship by user instead of by IP.$result5
to obtain the data for theuser_id
one that is making the connection.login_attempts
the attributetime
or assign it at a very distant date with the lock.$message
it should show the contact admin message when it hits 5 errors before you could show the number of times it failed.I think that the best way to approach this type of problem is by using a class to be able to have a minimum abstraction.
I will show you a basic example trying not to complicate it too much so that you can easily understand the code, do it quickly so it has some shortcomings.
The class allows us to:
The denial of access and blocking of access is performed on the user without taking into account the IP, this does not mean that a method cannot be implemented for this purpose.
We should have the following tables in our database:
users
login_log
We create a basic class for the connection to the database
Class Connection
LoginController class
Basic use of the class
Hello, I will tell you how we have secured a system that manages premium video content and prevents double use as well as illegitimate use.
Boards:
This table records the different users, registration date, password modification date, etc.
We use this table to manage the erroneous login failures, we save the data that you have sent in the form, we associate a user if possible, in our case we use Cloudflare that provides us with a rich user ID that is quite successful, and not very avoidable, hehe, and to prevent incorrect logins we use this data, as well as the user agent and the session id instead of the ip. We use escalated validation, that is, we reduce factors to block the user depending on the number of attempts.
In this table we store the different sessions as well as their status, we call the session the moment the user logs in and it is ok, first we verify that there are no active sessions, comparing the End date to see if it is closed or not. In our case we have an option to buy a subscription with access to up to 5 devices, in this case the maximum number of sessions is 5.
Within our platform, as it is a streaming retransmission, continuous requests are made, so that sessions with more than 120 seconds from the current moment are understood as closed.
Other active protection systems In
addition to the above, we have several active security systems to prevent unauthorized use or account sharing.
Seguimiento de dispositivos.
Utilizando el user Agent y el proveedor de la IP (Movistar, Orange, Vodafone, etc) hemos creado una tabla devices que les asignamos a un usuario, de tal manera que cuando accede desde un dispositivo que anteriormente no ha accedido solicitamos una doble verificación del usuario. Otra funcionalidad es que no pueda acceder desde otros dispositivos. El asunto del proveedor de servicios aún está en pruebas pero apunta maneras XD Aquí también se ha planteado utilizar la resolución dela pantalla, ya que habitualmente vemos las series y películas en pantalla completa, pero aún estamos en pruebas.
Doble factor de autentificación
Para cosas "raras" cambio de password, cambio de email, nuevos dispositivos, ip de comunidad distinta, etc solicitamos una doble autentificación, bien por email, authy, SMS, llamada, etc.
Uso de WAF
No tengo acciones de Cloudflare, aunque me gustaría, pero en mi concepto de ciberseguridad es fundamental.
Por último te diría que la ciberseguridad hoy en día es fundamental en cualquier proyecto, por lo que debería ser gestionada y supervisada por una empresa especializada en este ámbito.
He realizado ajustes a tu código implementado lo que solicitas:
La cantidad de intentos esta en una variable así podrás ajustarlo. Si no quieres que bloquee la IP podrias ponerle a la variable
$intentosIP
= 1000 por ejemplo, o ajustar el codigo :)Me gustaría aclarar que para realizar esto hay muchas formas, frameworks, librerías, patrones, etc. Me he limitado a seguir el estilo de tu código y he trabajado sobre ello, pero queda la recomendación de usar por ejemplo Programación Orientada a Objetos, evitar Inyección SQL, etc.
Base de Datos Se mantuvo la Base de Datos publicada, solo a la tabla
users
se le agrego el campologindatetime
de tipodatetime
que servira para validar el doble logueo.fail_attempt
login_attempts
users
"4e46dc0969e6621f2d61d2228e3cd91b75cd9edc" es "Hola" en
sha1
, al loguerase solo escribir "Hola", PHP lo convertira en sha1 para la comparacion con BD.Cada vez que se agregue un usuario usar la funcion
sha1
, ejemplo:Doble logueo
Usara el campo
logindatetime
de la tablausers
. Cuando el usuario se loguea es redireccionado a la paginaindex.php
(codigo original).En la pagina index.php se invoca un
ajax
que actualiza el campologindatetime
para saber que el usuario ha sido logueado satisfactoriamente. Tambien hay un botonCerrar Sesion
que limpia el campologindatetime
, de modo que cuando el campo esta NULL sabemos que el usuario ya no esta logueado.Para el caso que el usuario cierre el navegador sin antes usar el boton
Cerrar Sesion
, ha considerado invocar alajax
cada 1 minuto. De modo que silogindatetime
tiene una hora de mas de 2 minutos se entenderia que fue una sesion abandonada y se consideraria como que el usuario ya no esta logueado.Solucion
Para correr este codigo, los archivos den estar dentro de una carpeta
login
captcha.php Para generar dinamicamente la imagen captcha
requiere de esta imagen
login.php Codigo publicado reajustado
index.php Pagina de Bienvenida, luego del login.
logueado.php Actualizara el campo
logindatetime
y cerrara sesionCaptura
Podrían surgir mas casos de uso o validaciones que ajustarían mas el código. Si tienes dudas, conversamos por lo comentarios.
Actually blocking the IP is not a good idea, since 99% of users have dynamic IPs and only restarting the router would allow me to get a new IP.
One option would be to carry out a more specific control, but the implementation requires analysis since, as in any system; the more complex, the more error-prone and more expensive to maintain.
USER STATUS
When performing the logon the user could save an online state in the base and while the state is present no one else can connect to the system. It refreshes after a few minutes without user interaction on the site or when logoffing.
ACTIVITY ANALYSIS
You could implement an activity table for each user, in which the IP's and GEOlocation from which they connect to the system are registered, from it you would have to generate reports and analyze or buy a system that performs this type of analysis.
LOGIN WITH DOUBLE AUTHENTICATION
There are several ways to implement it, for example a Token with a series of numbers which you must enter, these can be sent through an email message, APP or by SMS.
SOCIAL ENGINEERING
Link access to your platform through a Facebook/Instagram/Linekdin/etc account. This type of connection with social networks reduces the possibility of sharing accounts.
Depending on the criticality and scope of your system, these security projects are no longer optional to protect both the company and the customer.