security - usuario - sistema de login php
¿Cómo puedo acelerar los intentos de inicio de sesión de usuario en PHP? (12)
Acabo de leer esta publicación La guía definitiva para la autenticación del sitio basada en formularios en Prevención de intentos de inicio de sesión de Rapid-Fire.
Práctica recomendada n. ° 1: un breve retraso que aumenta con el número de intentos fallidos, como:
1 intento fallido = sin demora
2 intentos fallidos = 2 segundos de retraso
3 intentos fallidos = retraso de 4 segundos
4 intentos fallidos = 8 segundos de retraso
5 intentos fallidos = 16 segundos de retraso
etc.
DoS atacar este esquema sería muy poco práctico, pero por otro lado, potencialmente devastador, ya que el retraso aumenta exponencialmente.
Tengo curiosidad de cómo podría implementar algo como esto para mi sistema de inicio de sesión en PHP?
Almacenar intentos fallidos en la base de datos por IP. (Dado que tienes un sistema de inicio de sesión, supongo que sabes cómo hacerlo).
Obviamente, las sesiones son un método tentador, pero alguien realmente dedicado puede darse cuenta fácilmente de que puede simplemente eliminar su cookie de sesión en intentos fallidos para eludir por completo el acelerador.
Al intentar iniciar sesión, busque cuántos intentos de inicio de sesión recientes (por ejemplo, los últimos 15 minutos) hubo, y la hora del último intento.
$failed_attempts = 3; // for example
$latest_attempt = 1263874972; // again, for example
$delay_in_seconds = pow(2, $failed_attempts); // that''s 2 to the $failed_attempts power
$remaining_delay = time() - $latest_attempt - $delay_in_seconds;
if($remaining_delay > 0) {
echo "Wait $remaining_delay more seconds, silly!";
}
El proceso de inicio de sesión necesita reducir su velocidad para el inicio de sesión exitoso y no exitoso. El intento de inicio de sesión nunca debe ser más rápido que aproximadamente 1 segundo. Si lo es, la fuerza bruta usa el retraso para saber que el intento falló porque el éxito es más corto que la falla. Entonces, se pueden evaluar más combinaciones por segundo.
El equilibrador de carga debe limitar el número de intentos de inicio de sesión simultáneos por máquina. Finalmente, solo necesita rastrear si el mismo usuario o contraseña es reutilizado por más de un intento de inicio de sesión de usuario / contraseña. Los humanos no pueden escribir más rápido que alrededor de 200 palabras por minuto. Por lo tanto, los intentos de inicio de sesión sucesivos o simultáneos de más de 200 palabras por minuto provienen de un conjunto de máquinas. Por lo tanto, se pueden canalizar a una lista negra de forma segura ya que no es su cliente. Los tiempos de lista negra por host no necesitan ser mayores de 1 segundo. Esto nunca molestará a un humano, pero causa estragos con un intento de fuerza bruta ya sea en serie o en paralelo.
2 * 10 ^ 19 combinaciones en una combinación por segundo, se ejecutan en paralelo en 4 mil millones de direcciones IP separadas, tomará 158 años para agotarse como un espacio de búsqueda. Para durar un día por usuario contra 4 mil millones de atacantes, necesita una contraseña alfanumérica completamente aleatoria de 9 lugares de duración como mínimo. Considere capacitar a los usuarios en frases de paso de al menos 13 lugares, 1.7 * 10 ^ 20 combinaciones.
Esta demora motivará al atacante a robar su archivo hash de contraseña en lugar de forzar su sitio de forma brutal. Use técnicas de hash aprobadas, nombradas. Prohibir a toda la población de IP de Internet por un segundo limitará el efecto de los ataques paralelos sin un trato que un ser humano aprecie. Finalmente, si su sistema permite más de 1000 intentos de inicio de sesión fallidos en un segundo sin alguna respuesta a los sistemas de prohibición, entonces sus planes de seguridad tienen problemas mayores para trabajar. Repare esa respuesta automatizada antes que nada.
En general, creo el historial de inicio de sesión y las tablas de intentos de inicio de sesión. La tabla de intentos registraría el nombre de usuario, la contraseña, la dirección IP, etc. Consulta la tabla para ver si necesitas retrasar. Recomiendo bloquear por completo para intentos mayores a 20 en un tiempo dado (una hora por ejemplo).
En mi humilde opinión, la defensa contra los ataques DOS se trata mejor en el nivel del servidor web (o incluso en el hardware de la red), no en su código PHP.
La respuesta corta es: No hagas esto. No te protegerás del forzamiento bruto, incluso podrías empeorar tu situación.
Ninguna de las soluciones propuestas funcionaría. Si usa la IP como parámetro para la aceleración, el atacante solo extenderá el ataque a través de una gran cantidad de direcciones IP. Si usa la sesión (cookie), el atacante simplemente eliminará cualquier cookie. La suma de todo lo que puedes pensar es que no hay absolutamente nada que un atacante de fuerza bruta no pueda superar.
Sin embargo, hay una cosa: simplemente confía en el nombre de usuario que intentó iniciar sesión. Por lo tanto, al no mirar todos los demás parámetros, puedes rastrear la frecuencia con la que un usuario intentó iniciar sesión y acelerar. Pero un atacante quiere hacerte daño. Si él reconoce esto, simplemente también utilizará los nombres de usuario de la fuerza bruta.
Esto dará como resultado que casi todos sus usuarios sean acelerados a su valor máximo cuando intenten iniciar sesión. Su sitio web será inútil. Atacante: éxito.
Podría retrasar la verificación de la contraseña en general durante alrededor de 200 ms: el usuario del sitio web casi no lo notará. Pero una fuerza bruta lo hará. (Nuevamente podría abarcar IP) Sin embargo, nada de todo esto lo protegerá del uso de fuerza bruta o DDoS, ya que no puede programarlo.
La única forma de hacerlo es usar la infraestructura.
Deberías usar bcrypt en lugar de MD5 o SHA-x para cifrar tus contraseñas, esto hará que descifrar tus contraseñas sea mucho más difícil si alguien roba tu base de datos (porque supongo que estás en un host compartido o administrado)
Perdón por decepcionarte, pero todas las soluciones aquí tienen una debilidad y no hay forma de superarlas dentro de la lógica de back-end.
Las cookies o los métodos basados en sesiones son, por supuesto, inútiles en este caso. La aplicación debe verificar la dirección IP o las marcas de tiempo (o ambos) de los intentos de inicio de sesión anteriores.
Se puede eludir una verificación de IP si el atacante tiene más de una dirección IP para iniciar sus solicitudes y puede ser problemático si varios usuarios se conectan a su servidor desde la misma IP. En este último caso, alguien que no inicie sesión varias veces evitará que todos los que comparten la misma IP inicien sesión con ese nombre de usuario durante un cierto período de tiempo.
Una verificación de fecha y hora tiene el mismo problema que el anterior: todos pueden evitar que todos los demás inicien sesión en una cuenta en particular simplemente intentándolo varias veces. Usar un captcha en lugar de una larga espera para el último intento probablemente sea una buena solución.
Las únicas cosas adicionales que debe evitar el sistema de inicio de sesión son las condiciones de carrera en la función de comprobación de intentos. Por ejemplo, en el siguiente pseudocódigo
$time = get_latest_attempt_timestamp($username);
$attempts = get_latest_attempt_number($username);
if (is_valid_request($time, $attempts)) {
do_login($username, $password);
} else {
increment_attempt_number($username);
display_error($attempts);
}
¿Qué sucede si un atacante envía solicitudes simultáneas a la página de inicio de sesión? Probablemente todas las solicitudes se ejecutarán con la misma prioridad, y es probable que ninguna solicitud llegue a la instrucción increment_attempt_number antes de que las otras pasen la segunda línea. De modo que cada solicitud obtiene el mismo valor de $ tiempo y $ intentos y se ejecuta. La prevención de este tipo de problemas de seguridad puede ser difícil para aplicaciones complejas e implica bloquear y desbloquear algunas tablas / filas de la base de datos, por supuesto ralentizando la aplicación.
Puedes usar sesiones Cada vez que el usuario no inicia sesión, aumenta el valor que almacena la cantidad de intentos. Puede calcular la demora requerida a partir del número de intentos, o puede establecer el tiempo real que el usuario puede intentar nuevamente en la sesión también.
Un método más confiable sería almacenar los intentos y new-try-time en la base de datos para ese particular ipaddress.
Según el análisis anterior, las sesiones, las cookies y las direcciones IP no son efectivas, todo puede ser manipulado por el atacante.
Si desea evitar los ataques de fuerza bruta, la única solución práctica es basar el número de intentos en el nombre de usuario proporcionado, sin embargo, tenga en cuenta que esto permite al atacante a DOS el sitio al bloquear el inicio de sesión de usuarios válidos.
p.ej
$valid=check_auth($_POST[''USERNAME''],$_POST[''PASSWD'']);
$delay=get_delay($_POST[''USERNAME''],$valid);
if (!$valid) {
header("Location: login.php");
exit;
}
...
function get_delay($username,$authenticated)
{
$loginfile=SOME_BASE_DIR . md5($username);
if (@filemtime($loginfile)<time()-8600) {
// last login was never or over a day ago
return 0;
}
$attempts=(integer)file_get_contents($loginfile);
$delay=$attempts ? pow(2,$attempts) : 0;
$next_value=$authenticated ? 0 : $attempts + 1;
file_put_contents($loginfile, $next_value);
sleep($delay); // NB this is done regardless if passwd valid
// you might want to put in your own garbage collection here
}
Tenga en cuenta que, tal como está escrito, este procedimiento filtra información de seguridad, es decir, alguien que está atacando el sistema puede ver cuándo un usuario inicia sesión (el tiempo de respuesta del intento de los atacantes se reducirá a 0). También puede ajustar el algoritmo para que el retraso se calcule en función de la demora anterior y la marca de tiempo en el archivo.
HTH
DO.
Tiene tres enfoques básicos: almacenar información de sesión, almacenar información de cookies o almacenar información de IP.
Si usa información de la sesión, el usuario final (atacante) podría invocar forzosamente nuevas sesiones, omitir su táctica y luego volver a iniciar sesión sin demora. Las sesiones son bastante simples de implementar, simplemente almacenan el último tiempo de inicio de sesión conocido del usuario en una variable de sesión, coinciden con la hora actual y asegúrese de que la demora haya sido lo suficientemente larga.
Si usa cookies, el atacante puede simplemente rechazar las cookies, en general, esto realmente no es algo viable.
Si rastrea las direcciones IP, deberá almacenar los intentos de inicio de sesión desde una dirección IP de alguna manera, preferiblemente en una base de datos. Cuando un usuario intenta iniciar sesión, simplemente actualice su lista de direcciones IP grabadas. Debe purgar esta tabla a un intervalo razonable, volcando las direcciones IP que no han estado activas en algún momento. La trampa (siempre hay una trampa) es que algunos usuarios pueden terminar compartiendo una dirección IP y, en condiciones de frontera, sus retrasos pueden afectar a los usuarios inadvertidamente. Como realiza un seguimiento de los inicios de sesión fallidos y solo ha fallado el inicio de sesión, esto no debería causar demasiado dolor.
cballuo brindó una excelente respuesta. Solo quería devolver el favor al proporcionar una versión actualizada que admite mysqli. Cambié ligeramente las columnas de mesa / campo en los sqls y otras cosas pequeñas, pero debería ayudar a cualquiera que busque el equivalente de mysqli.
function get_multiple_rows($result) {
$rows = array();
while($row = $result->fetch_assoc()) {
$rows[] = $row;
}
return $rows;
}
$throttle = array(10 => 1, 20 => 2, 30 => 5);
$query = "SELECT MAX(time) AS attempted FROM failed_logins";
if ($result = $mysqli->query($query)) {
$rows = get_multiple_rows($result);
$result->free();
$latest_attempt = (int) date(''U'', strtotime($rows[0][''attempted'']));
$query = "SELECT COUNT(1) AS failed FROM failed_logins WHERE time > DATE_SUB(NOW(),
INTERVAL 15 minute)";
if ($result = $mysqli->query($query)) {
$rows = get_multiple_rows($result);
$result->free();
$failed_attempts = (int) $rows[0][''failed''];
krsort($throttle);
foreach ($throttle as $attempts => $delay) {
if ($failed_attempts > $attempts) {
echo $failed_attempts;
$remaining_delay = (time() - $latest_attempt) - $delay;
if ($remaining_delay < 0) {
echo ''You must wait '' . abs($remaining_delay) . '' seconds before your next login attempt'';
}
break;
}
}
}
}
No se puede evitar simplemente los ataques DoS encadenando la aceleración a una única IP o nombre de usuario. Diablos, ni siquiera puedes evitar intentos de inicio de sesión rápidos usando este método.
¿Por qué? Debido a que el ataque puede abarcar múltiples IP y cuentas de usuario por el simple hecho de eludir sus intentos de aceleración.
He visto publicado en otro lugar que, idealmente, debería hacer un seguimiento de todos los intentos de inicio de sesión fallidos en el sitio y asociarlos a una marca de tiempo, tal vez:
CREATE TABLE failed_logins (
id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(16) NOT NULL,
ip_address INT(11) UNSIGNED NOT NULL,
attempted DATETIME NOT NULL,
INDEX `attempted_idx` (`attempted`)
) engine=InnoDB charset=UTF8;
Una nota rápida en el campo ip_address: puede almacenar los datos y recuperar los datos, respectivamente, con INET_ATON () e INET_NTOA () que equivalen esencialmente a convertir una dirección IP a un entero sin signo y a partir de este.
# example of insertion
INSERT INTO failed_logins SET username = ''example'', ip_address = INET_ATON(''192.168.0.1''), attempted = CURRENT_TIMESTAMP;
# example of selection
SELECT id, username, INET_NTOA(ip_address) AS ip_address, attempted;
Decidir sobre ciertos umbrales de retraso basados en el número total de inicios de sesión fallidos en un período de tiempo determinado (15 minutos en este ejemplo). Debe basar esto en los datos estadísticos extraídos de la tabla failed_logins
, ya que cambiarán con el tiempo según la cantidad de usuarios y cuántos de ellos pueden recuperar (y escribir) su contraseña.
> 10 failed attempts = 1 second
> 20 failed attempts = 2 seconds
> 30 failed attempts = reCaptcha
Consulte la tabla en cada intento fallido de inicio de sesión para encontrar el número de inicios de sesión fallidos durante un período de tiempo determinado, digamos 15 minutos:
SELECT COUNT(1) AS failed FROM failed_logins WHERE attempted > DATE_SUB(NOW(), INTERVAL 15 minute);
Si el número de intentos durante el período de tiempo dado supera su límite, aplique la limitación o fuerce a todos los usuarios a usar un captcha (es decir, volver a capturar) hasta que el número de intentos fallidos durante el período de tiempo determinado sea menor que el umbral.
// array of throttling
$throttle = array(10 => 1, 20 => 2, 30 => ''recaptcha'');
// retrieve the latest failed login attempts
$sql = ''SELECT MAX(attempted) AS attempted FROM failed_logins'';
$result = mysql_query($sql);
if (mysql_affected_rows($result) > 0) {
$row = mysql_fetch_assoc($result);
$latest_attempt = (int) date(''U'', strtotime($row[''attempted'']));
// get the number of failed attempts
$sql = ''SELECT COUNT(1) AS failed FROM failed_logins WHERE attempted > DATE_SUB(NOW(), INTERVAL 15 minute)'';
$result = mysql_query($sql);
if (mysql_affected_rows($result) > 0) {
// get the returned row
$row = mysql_fetch_assoc($result);
$failed_attempts = (int) $row[''failed''];
// assume the number of failed attempts was stored in $failed_attempts
krsort($throttle);
foreach ($throttle as $attempts => $delay) {
if ($failed_attempts > $attempts) {
// we need to throttle based on delay
if (is_numeric($delay)) {
$remaining_delay = time() - $latest_attempt - $delay;
// output remaining delay
echo ''You must wait '' . $remaining_delay . '' seconds before your next login attempt'';
} else {
// code to display recaptcha on login form goes here
}
break;
}
}
}
}
El uso de reCaptcha en un cierto umbral garantizaría que un ataque desde múltiples frentes se detuviera y que los usuarios normales del sitio no experimenten un retraso significativo para los intentos de inicio de sesión fallidos legítimos.
session_start();
$_SESSION[''hit''] += 1; // Only Increase on Failed Attempts
$delays = array(1=>0, 2=>2, 3=>4, 4=>8, 5=>16); // Array of # of Attempts => Secs
sleep($delays[$_SESSION[''hit'']]); // Sleep for that Duration.
o según lo sugerido por Cyro:
sleep(2 ^ (intval($_SESSION[''hit'']) - 1));
Es un poco duro, pero los componentes básicos están ahí. Si actualiza esta página, cada vez que actualice la demora se hará más larga.
También puede mantener los recuentos en una base de datos, donde puede verificar el número de intentos fallidos por IP. Al usarlo en base a IP y mantener los datos de su lado, evita que el usuario pueda borrar sus cookies para detener el retraso.
Básicamente, el código de inicio sería:
$count = get_attempts(); // Get the Number of Attempts
sleep(2 ^ (intval($count) - 1));
function get_attempts()
{
$result = mysql_query("SELECT FROM TABLE WHERE IP=/"".$_SERVER[''REMOTE_ADDR'']."/"");
if(mysql_num_rows($result) > 0)
{
$array = mysql_fetch_assoc($array);
return $array[''Hits''];
}
else
{
return 0;
}
}