c# - Solicitud de bloqueo para múltiples inicios de sesión fallidos por un período de tiempo
asp.net security (7)
Tengo un sitio web y deseo bloquear la solicitud de
BOTs
e intentar iniciar sesión en la fuerza bruta en mi sitio web.
Ahora estoy usando
Session
para almacenar el intento de inicio de sesión y mostrar captcha después de 3 inicios de sesión fallidos, pero hay un problema. La sesión se eliminará si el usuario cierra el navegador.
¿Qué tipo de solución debo considerar para evitar
BOTs
y el inicio de sesión de fuerza bruta?
¿Qué propiedad del sistema de usuario o navegador debo almacenar para administrar su próximo inicio de sesión?
Editar 1)
No uso el proveedor de membresía ASP.NET. Estoy usando mis propias clases de autenticación y autorización.
Identifique un inicio de sesión no válido basado en IpAddress (proxy anónimo). Que cada IP de inicio de sesión no válida y el número de cuenta y hora de inicio de sesión que se almacenarán en el estado de la aplicación.
Crear clase InvalidLogin
public class InvalidLogin
{
public string IP { get; set; }
public DateTime Attempttime { get; set; }
public int AttemptCount { get; set; }
}
Evento de inicio de sesión
protected void Login_Click(object sender, EventArgs e)
{
bool Testsuccessfullogin = false;
if (Testsuccessfullogin)
{
//Your code after successfull login
}
else
{
//Invalid Login --- Capture Each login event based on IP
string strIp;
strIp = Request.ServerVariables["HTTP_X_FORWARDED_FOR"]; //when user is behind proxy server
if (strIp == null)
{
strIp = Request.ServerVariables["REMOTE_ADDR"];//Without proxy
}
List<InvalidLogin> user = null;
if (HttpContext.Current.Application["Users"] == null) //Adding List to Application State
{
user = new List<InvalidLogin>();
}
else
{
user = (List<InvalidLogin>)HttpContext.Current.Application["Users"];
}
var remove = user.RemoveAll(x => x.Attempttime < DateTime.Now.AddMinutes(-15));//Remove IP Before 15 minutes(Give 15 Min Time Next Login)
var checkLogged = user.Find(x => x.IP == strIp);
if (checkLogged == null)
{
user.Add(new InvalidLogin
{
IP = strIp,
Attempttime = DateTime.Now,
AttemptCount = 1
});
Application.Lock();
HttpContext.Current.Application["Users"] = user;
Application.UnLock();
}
else
{
if (checkLogged.AttemptCount < 4)
{
checkLogged.Attempttime = DateTime.Now;
checkLogged.AttemptCount++;
Application.Lock();
HttpContext.Current.Application["Users"] = user;
Application.UnLock();
}
}
if (checkLogged != null)
{
if (checkLogged.AttemptCount > 3)
{
captcha.Visible = true; //Showing captcha
}
}
}
}
Lo único que me gustaría agregar que otros no tienen es que, cuando sea posible, no desea alertar a los bots sobre el hecho de que han sido detectados. si los bloquea con algún mensaje, simplemente tomarán nota de lo que hicieron para ser detectados y ajustados. por ejemplo, si "los notas" por ip, simplemente no permitas que la contraseña que ingresan tenga éxito. se dejarán engañar pensando que tienes contraseñas complicadas, etc., y se irán a otro lado, sin saber nunca con certeza que las has notado.
También sugeriría almacenar los "intentos" en una base de datos con ip. luego puede volver fácilmente y revisar los intentos realizados en su sitio. podría consultar los registros web, pero eso es más doloroso. También registro inicios de sesión exitosos para poder notar cuándo entran los bots para volver y aplicar más investigaciones.
Lo más fácil sería presentar su solución con un proveedor de CDN como cloudflare ( https://www.cloudflare.com/features-security ) que detectará los bots por usted. Muchos CDN ofrecen esto, y Cloudflare tiene una tarifa gratuita.
Alternativamente, si está intentando hacerlo usted mismo, puede contar el número de intentos por nombre de usuario en su base de datos y presentar un captcha basado en este recuento.
No puede usar la sesión, ya que requiere que el cliente almacene una cookie para usted, y un atacante no lo ayudará. Necesitarás un estado global.
No necesita molestarse en rastrear direcciones IP, ya que un tipo malo solo usará un Proxy de anonimato.
No use el bloqueo de cuenta a menos que tenga que hacerlo (requisito de PCI), ya que esto solo le permite al atacante hacer DoS a sus usuarios.
También debe evitar hacer DoS usted mismo haciendo que su servidor haga demasiado trabajo.
Esto funciona:
Tras una autenticación fallida, almacene el nombre de usuario en estado global, junto con el recuento.
count++
sincronizado
count++
si hay más autenticaciones fallidas con ese nombre de usuario.
Yo uso redis para esto.
Si
count >= threshold
, exija el valor CAPTCHA resuelto antes de continuar.
Mostrar CAPTCHA en la pantalla de inicio de sesión.
Tras una autenticación exitosa , borre el nombre de usuario almacenado en estado global. Proporcione al usuario "agente de usuario de confianza" cookie HMAC''d, para que no tengan que CAPTCHA en el futuro para ese nombre de usuario en ese UA.
Puede hacer lo mismo con las contraseñas , pero probablemente con un umbral más alto.
Si no le gusta CAPTCHA , exija Prueba de trabajo, por ejemplo, haciendo que el cliente calcule y envíe los factores primos de un número muy grande.
Mientras lo hace, asegúrese de usar bcrypt para cifrar sus contraseñas, y que el factor de costo sea lo suficientemente alto como para que se requiera> = 250 ms para descifrar una contraseña. Esto ralentiza su servidor pero también ralentiza a un atacante. Evite el hash a menos que pasen el CAPTCHA (si es necesario).
¿Animar a los usuarios a usar largos, complicados, memorables? contraseñas, por lo que son más difíciles de fuerza bruta.
Puede suspender la cuenta después de algunos intentos fallidos y hacer que el usuario responda preguntas de seguridad para volver a habilitarla. Además, no permita la reutilización de unas últimas contraseñas y debe estar seguro.
Dicho esto, si desea hacerlo a través de la codificación, guarde el tercer tiempo de intento de inicio de sesión
[MaxAttemptTime]
(
DateTime.Now
) y el tiempo para liberar la cuenta
[ReleaseTime]
(digamos después de 20 minutos
DateTime.Now.AddMinutes(20)
) .
Ahora, cada vez que el mismo usuario intenta iniciar sesión, debe rechazarse en función del
[ReleaseTime]
.
Restablezca estos contadores en el inicio de sesión exitoso para un usuario genuino.
Si está utilizando sus propias clases de autenticación y autorización, debe contar el número de intentos fallidos de inicio de sesión para cada usuario y su fecha y hora.
Si se alcanza el número de intentos, el límite es que interrumpirá el siguiente proceso de inicio de sesión con un mensaje de error como "Su cuenta fue bloqueada durante 15 minutos, intente nuevamente más tarde".
Por ejemplo. La tabla de inicios de sesión se denomina [Inicios de sesión].
You need to add new colums:
1. [LastLoginAttemptAt] DATETIME NULL
2. [LoginFailedAttemptsCount] INT NOT NULL DEFAULT 0
Entonces, el inicio de sesión de su clase tendrá estos nuevos campos:
public class Login {
public DateTime? LastLoginAttemptAt {get;set;}
public int LoginFailedAttemptsCount {get;set;}
}
También debe almacenar alguna variable de configuración: el valor del número máximo de intentos fallidos para iniciar sesión y bloquear el período.
const int MaxNumberOfFailedAttemptsToLogin = 10;
const int BlockMinutesAfterLimitFailedAttemptsToLogin = 15; // 15 min
En un método de inicio de sesión, hará lo siguiente (ejemplo primitivo de código, no una versión prod):
public void SignIn(string username, string password)
{
var login = _userService.TryGetLogin(username);
if (login == null){
// Login by username not found.
// Return error message "Invalid username or password"
return;
}
if (login.LoginFailedAttemptsCount > MaxNumberOfFailedAttemptsToLogin
&& login.LastLoginAttemptAt.HasValue
&& DateTime.Now < login.LastLoginAttemptAt.Value.AddMinutes(BlockMinutesAfterLimitFailedAttemptsToLogin))
{
// Login is blocked, need to break the process.
// Return error message "Your account was blocked
// for a 15 minutes, please try again later."
return;
} else {
login.LoginFailedAttemptsCount = 0;
login.LastLoginAttemptAt = DateTime.Now;
}
var success = login.ValidatePassword(password);
if (!success)
{
// Invalid password, need to update the number of attempts.
login.LastLoginAttemptAt = DateTime.Now; //or UTC
login.LoginFailedAttemptsCount++;
// Update(login);
// Return error message "Invalid username or password"
return;
} else {
login.LoginFailedAttemptsCount = 0;
login.LastLoginAttemptAt = DateTime.Now;
// Update(login);
// Success!
}
}
Si estuviera haciendo esto, usaría una columna en una base de datos para almacenar el número de intento de inicio de sesión y una marca de fecha y hora para el primer intento. Luego tenga algo de lógica alrededor del inicio de sesión
if(loginAttempt>5 && DateTime.Now<loginDate.AddMinute(5))
{
//Locked out
}