在一段时间内阻止多次登录失败的请求
Block request for multiple unsuccessful logins for a period of time
我有一个网站,我想阻止来自 BOTs
的请求并尝试暴力登录到我的网站。
现在我使用 Session
来存储登录尝试并在 3 次登录失败后显示验证码,但是如果用户关闭浏览器,problem.Session 将被删除。
我应该考虑什么样的解决方案来防止BOTs
和暴力登录?我应该存储什么 属性 的用户系统或浏览器来管理 his/her 下次登录?
编辑 1)
我不使用 ASP.NET 会员提供商。我正在使用自己的身份验证和授权 类
最简单的方法是使用 CDN 提供商(例如 cloudflare (https://www.cloudflare.com/features-security) 为您检测机器人)来解决您的问题。许多 CDN 都提供此服务,而 cloudflare 有免费关税。
或者,如果您自己尝试执行此操作,则可以计算数据库中每个用户名的尝试次数,并根据此计数显示验证码。
您不能使用会话,因为它需要客户端为您存储 cookie,而攻击者不会帮助您。您将需要一些全局状态。
您不必费心跟踪 IP 地址,因为坏人只会使用匿名代理。
除非必须(PCI 要求),否则不要 使用帐户锁定,因为这只会让攻击者对您的用户进行 DoS。
您还希望通过让您的服务器做太多工作来避免对自己进行 DoS 攻击。
这个有效:
身份验证不成功时,将用户名和计数一起存储在全局状态中。如果使用该用户名进行更多不成功的身份验证,则同步 count++
。我为此使用了redis。
如果count >= threshold
,则要求在继续之前解决CAPTCHA值。在登录屏幕上显示验证码。
成功 身份验证后,清除全局状态中存储的用户名。为用户提供 "trusted user agent" HMAC 的 cookie,这样他们以后就不必为该 UA 上的用户名验证码了。
您可以对密码执行相同的操作,但门槛可能更高。
如果您不喜欢 CAPTCHA,则需要工作量证明,例如让客户计算并提交一个非常大的数的质因数。
当您使用它时,请确保您正在使用 bcrypt 来散列您的密码,并且成本因素足够高,以至于散列密码需要 >= 250 毫秒。这会减慢您的服务器速度,但也会减慢攻击者的速度。避免散列,除非他们通过验证码(如果需要)。
鼓励用户使用长、复杂、好记?密码,这样它们就更难被暴力破解。
您可以在几次尝试失败后暂停帐户,并让用户回答安全问题以重新启用它。也不要允许重复使用最后几个密码,你应该是安全的。
既然有人说如果你想通过编码来做到这一点然后保存第三次登录尝试时间[MaxAttemptTime]
(DateTime.Now
)和释放帐户的时间[ReleaseTime]
(说之后20 分钟 DateTime.Now.AddMinutes(20)
).
现在每次同一用户尝试登录时,都应根据 [ReleaseTime]
拒绝。在真正用户成功登录后重置这些计数器。
唯一我想补充而其他人没有补充的一点是,在可能的情况下,您不想提醒机器人它们已被检测到。如果你用一些消息阻止他们,那么他们只会记下他们做了什么来检测和调整。例如,如果您通过 ip "noticing" 他们,只是不要让他们输入的密码成功。他们会被愚弄,以为您有一些复杂的密码等,然后去其他地方,却不确定您是否注意到了它们。
我还建议将 "attempts" 存储在具有 ip 的数据库中。然后您可以轻松地返回并查看针对您的网站所做的尝试。您可以查询网络日志,但这更痛苦。我还记录了成功的登录,这样我就可以注意到机器人何时返回并应用进一步的研究。
如果我这样做,我会在数据库中使用一个列来存储第一次尝试的登录尝试次数和日期时间戳。然后围绕登录进行一些逻辑
if(loginAttempt>5 && DateTime.Now<loginDate.AddMinute(5))
{
//Locked out
}
如果您使用自己的身份验证和授权classes,您需要计算每个用户登录失败的次数及其日期和时间。
如果尝试次数达到限制,您将中断下一次登录过程并显示错误消息,例如 "Your account was blocked for a 15 minutes, please try again later."
例如。
Table 个登录名为 [Logins]。
You need to add new colums:
1. [LastLoginAttemptAt] DATETIME NULL
2. [LoginFailedAttemptsCount] INT NOT NULL DEFAULT 0
因此,您的 class 登录名将包含以下新字段:
public class Login {
public DateTime? LastLoginAttemptAt {get;set;}
public int LoginFailedAttemptsCount {get;set;}
}
您还需要存储一些配置变量 - 最大登录尝试失败次数和阻止时间的值。
const int MaxNumberOfFailedAttemptsToLogin = 10;
const int BlockMinutesAfterLimitFailedAttemptsToLogin = 15; // 15 min
在登录方法中,您将执行以下操作(原始代码示例,而非产品版本):
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!
}
}
根据IpAddress(匿名代理)识别无效登录。将每个无效登录ip和登录次数&时间存储在Application State中。
创建Class无效登录
public class InvalidLogin
{
public string IP { get; set; }
public DateTime Attempttime { get; set; }
public int AttemptCount { get; set; }
}
登录事件
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
}
}
}
}
我有一个网站,我想阻止来自 BOTs
的请求并尝试暴力登录到我的网站。
现在我使用 Session
来存储登录尝试并在 3 次登录失败后显示验证码,但是如果用户关闭浏览器,problem.Session 将被删除。
我应该考虑什么样的解决方案来防止BOTs
和暴力登录?我应该存储什么 属性 的用户系统或浏览器来管理 his/her 下次登录?
编辑 1)
我不使用 ASP.NET 会员提供商。我正在使用自己的身份验证和授权 类
最简单的方法是使用 CDN 提供商(例如 cloudflare (https://www.cloudflare.com/features-security) 为您检测机器人)来解决您的问题。许多 CDN 都提供此服务,而 cloudflare 有免费关税。
或者,如果您自己尝试执行此操作,则可以计算数据库中每个用户名的尝试次数,并根据此计数显示验证码。
您不能使用会话,因为它需要客户端为您存储 cookie,而攻击者不会帮助您。您将需要一些全局状态。
您不必费心跟踪 IP 地址,因为坏人只会使用匿名代理。
除非必须(PCI 要求),否则不要 使用帐户锁定,因为这只会让攻击者对您的用户进行 DoS。
您还希望通过让您的服务器做太多工作来避免对自己进行 DoS 攻击。
这个有效:
身份验证不成功时,将用户名和计数一起存储在全局状态中。如果使用该用户名进行更多不成功的身份验证,则同步 count++
。我为此使用了redis。
如果count >= threshold
,则要求在继续之前解决CAPTCHA值。在登录屏幕上显示验证码。
成功 身份验证后,清除全局状态中存储的用户名。为用户提供 "trusted user agent" HMAC 的 cookie,这样他们以后就不必为该 UA 上的用户名验证码了。
您可以对密码执行相同的操作,但门槛可能更高。
如果您不喜欢 CAPTCHA,则需要工作量证明,例如让客户计算并提交一个非常大的数的质因数。
当您使用它时,请确保您正在使用 bcrypt 来散列您的密码,并且成本因素足够高,以至于散列密码需要 >= 250 毫秒。这会减慢您的服务器速度,但也会减慢攻击者的速度。避免散列,除非他们通过验证码(如果需要)。
鼓励用户使用长、复杂、好记?密码,这样它们就更难被暴力破解。
您可以在几次尝试失败后暂停帐户,并让用户回答安全问题以重新启用它。也不要允许重复使用最后几个密码,你应该是安全的。
既然有人说如果你想通过编码来做到这一点然后保存第三次登录尝试时间[MaxAttemptTime]
(DateTime.Now
)和释放帐户的时间[ReleaseTime]
(说之后20 分钟 DateTime.Now.AddMinutes(20)
).
现在每次同一用户尝试登录时,都应根据 [ReleaseTime]
拒绝。在真正用户成功登录后重置这些计数器。
唯一我想补充而其他人没有补充的一点是,在可能的情况下,您不想提醒机器人它们已被检测到。如果你用一些消息阻止他们,那么他们只会记下他们做了什么来检测和调整。例如,如果您通过 ip "noticing" 他们,只是不要让他们输入的密码成功。他们会被愚弄,以为您有一些复杂的密码等,然后去其他地方,却不确定您是否注意到了它们。
我还建议将 "attempts" 存储在具有 ip 的数据库中。然后您可以轻松地返回并查看针对您的网站所做的尝试。您可以查询网络日志,但这更痛苦。我还记录了成功的登录,这样我就可以注意到机器人何时返回并应用进一步的研究。
如果我这样做,我会在数据库中使用一个列来存储第一次尝试的登录尝试次数和日期时间戳。然后围绕登录进行一些逻辑
if(loginAttempt>5 && DateTime.Now<loginDate.AddMinute(5))
{
//Locked out
}
如果您使用自己的身份验证和授权classes,您需要计算每个用户登录失败的次数及其日期和时间。
如果尝试次数达到限制,您将中断下一次登录过程并显示错误消息,例如 "Your account was blocked for a 15 minutes, please try again later."
例如。 Table 个登录名为 [Logins]。
You need to add new colums:
1. [LastLoginAttemptAt] DATETIME NULL
2. [LoginFailedAttemptsCount] INT NOT NULL DEFAULT 0
因此,您的 class 登录名将包含以下新字段:
public class Login {
public DateTime? LastLoginAttemptAt {get;set;}
public int LoginFailedAttemptsCount {get;set;}
}
您还需要存储一些配置变量 - 最大登录尝试失败次数和阻止时间的值。
const int MaxNumberOfFailedAttemptsToLogin = 10;
const int BlockMinutesAfterLimitFailedAttemptsToLogin = 15; // 15 min
在登录方法中,您将执行以下操作(原始代码示例,而非产品版本):
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!
}
}
根据IpAddress(匿名代理)识别无效登录。将每个无效登录ip和登录次数&时间存储在Application State中。
创建Class无效登录
public class InvalidLogin
{
public string IP { get; set; }
public DateTime Attempttime { get; set; }
public int AttemptCount { get; set; }
}
登录事件
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
}
}
}
}