How to lock user account on 5 unsuccessful attempts
I have a website developed using asp.net/C#. I would like to lock an user account on 5 consecutive login failures within a time period of 30 minutes. I do not want to do this on database side. And I know this is cannot be done by session variables. I also do not want to use cookies for this, as a user can easily disable cookies.
Is there a perfect way 开发者_开发技巧to do this with above limitations?
So, no cookies, no session data, no database. Okay, but you need to store the bad-login information somewhere. And it will have to be server-side, because you can't trust any data from the client. So that means a database, or a file, or a magic hat — but you'll need something on a server somewhere holding this data. Options I see:
- Your own server
- Database (I know you said you didn't want this, but it's the right thing)
- File
- ...
- Someone else's server
- Database (Amazon SimpleDB?)
- Google docs spreadsheet (but again, a database is what you really need)
- File in an S3 or similar container
You get the idea.
Short answer
Track the number of consecutive failed attempts for a given IP address as well as any given Account ID (username/email). Use a table of failed attempts with columns for IP, date and the account ID. Limit the number of attempted logins over a period of time.
Long answer
Clients cannot be trusted
You are correct in stating that you cannot use cookies or session state (which is persisted by a cookie) for this, since an attacker can simply use a fresh cookie or none at all for every attempt, thus fooling your system. Under no circumstances should this be done client-side, as proposed by another answer. The client should never be trusted. But you need to track an attacker somehow, and the only practical way to do that without using cookies, is via their IP address.
Tracking IP's & Limiting Attempts
IP addresses can be spoofed, but a motivated attacker capable of this en masse is likely to use more sophisticated methods anyhow. You will need to log each login attempt by IP and on every attempt, check if your attempts log contains at least 5 or more attempts within the last N hours for the attempted account. You may also want to limit the total number of attempts for any account from a given IP to prevent an attacker from brute-forcing the same combination on multiple accounts over a period of several hours.
Locking Accounts
Optionally, you can lock the offending account for several hours (though I am averse to this - your users should never suffer due to your inadequate security) after several consecutive attempts. Bear in mind that any form of successful social engineering and password re-use will thwart your best attempts, so enforcing a strong password policy is paramount.
Automated Password Resets
There are other secondary measures you can take to make attempts statistically not worthwhile, such as automated password resets, with access links sent to the user account. These types of actions would depend on the nature of your product and what you are trying to protect. Banking sites lock accounts with too many consecutive incorrect attempts, for example, because of the severity of a compromised account.
Use a CAPTCHA
Perhaps the simplest deterrent I would prescribe is to demand a strong CAPTCHA (I recommend ReCAPTCHA) after 3 consecutive failed attempts from any given IP regardless of the attempted username/email. Users behind the same IP may have to enter a CAPTCHA now and then due to other users' failed attempts, but this is a small price to pay for security. As an attacker confronted by ReCAPTCHA, I would simply give up.
Delay Attempts
A less impinging approach is to limit the number of password attempts by introducing a synthetic authentication delay after 3 consecutive failed attempts. This reduces the viability of a brute-force attack by limiting the number of attempts over N hours which, when combined with an enforced password expiry policy will thwart brute-force attacks.
On a sidenote, make sure you store only salted hashes of user's passwords, with different salts for each password and reject common passwords and dictionary words.
Regardless of how you persist your data, you will probably want to create a subclass of the ASP.Net MembershipProvider in order to tie into all the nice ASP.Net membership things.
See the MSDN for information on SqlMembershipProvider as an example. You could use any ASP.Net membership provider as a base, or even implement your own membership provider directly by subclassing the base abstract class.
As pointed out by @Novikov in comments below, you just use the MaxInvalidPasswordAttempts
and PasswordAttemptWindow
to achieve your desired functionality.
Here's an example class:
public class MyMagicCloudMembershipProvider : System.Web.Security.MembershipProvider
{
public override bool ValidateUser(string username, string password)
{
return ToTheCloud.WithWindowsSeven(user, password);
}
}
This then plays nicely with your web.config - you simply provide the type of your membership provider and then everything uses this. You can then use all the default webforms login controls, and ASP.Net will call the methods in your membership provider.
I agree with many of the posts above and their suggestions. I implement this feature on all of our companies websites and use the database to do it.
Please implement this as a feature on the database with your site and then you can go on and use this code in every site you build from now on without any effort using your exising code and not the hack i suggest.
That said to solve your problem with your given limitations I propose the following.
Store an object in your application / web cache. This can be a list, an example might be a list of failed login attempts.
Bit of psuedo code...
List
public class LoginAttempt { public string IP {get;set;} public DateTime AttemptTime {get;set;} }
Initialise this list of obeject in your global.asax application start event.
Now add an entry to this object when you get a failed attempt.
Now you can just use some LINQ to do some checking when someone tried to loggin.
if(CachedLoginAttempts.Where(x => x.IP && x.AttemptTime > DateTime.Now.AddMinutes(-how long you want to lock for)).Count() > how many failed login attempts you want to allow) { Let In }
Take a look at this page to get an good idea about how to use caching in .NET if you havent used it before.
http://www.aspnettutorials.com/tutorials/network/web-caching-csharp.aspx
You can use JavaScript for this (which is not a good solution) . You can use onload() and onunload() events to submit the page (TO ITSELF) and propagate the state. But as a user would only have to clear out their browser to defeat this.
As everyone has already mentioned, this data needs to be stored somewhere. The best place to store this information is on your server. You can store this information within cookies (which is not a good idea), you can store this information within form variables submitted to your server (a better idea), you can store this information within form variables submitted to yourself (my terrible idea) or you can store them somewhere else (T.J. Crowder's good idea). Where do you want to store them?
The only thing I can think of that adheres to your limitations is using a file. If you can't modify your Database Model, and can't rely on any other external way of persisting the information, that's what you gotta do.
Of course, you can't simply have a plain txt in your server and call it done -- you will have a lot of problems dealing with how you access that file in a multi-user scenario, or what happens if you have a hardware problem just when you where about to write to the file. Moreover, what happens if the file becomes corrupt? What about encription, is a plain txt safe enough to hold all your users names?
So, you'd have to write a component that manages this file. Probably a singleton that lazy loads the file and rewrite it to disk whenever necessary. It might enqueue requisitions, but if you have a large user community (>1000), you will have a performance bottleneck right there. So maybe you don't write the file every time something changes, or maybe you write many files so that you avoid concurrence. But you will need an heuristic to determine which file to write and...
And see where I'm going? Your best option is to write a mini DBMS by yourself, and assume the risks of using such a weak approach. However, I've been there and done something similar, but it wasn't even closely related to the security of my application, and I have a very nice restriction: there would be no more than ONE record in the file.
精彩评论