Page 1 of 1

Throttling logins, locking out after N tries

Posted: Mon Jul 24, 2006 10:38 am
by bdlang
Hey all. I've searched around and about the best I can find on this is in Chris Shiflett's book with an example of throttling a user login to 15 seconds. There is a code example here.

Now that's all fine and well, but it only addresses an attack on a specific, valid user (note the logic is based on a matching user in the table, updating a 'last_failure' time for comparison in the script) and there is no implementation to lock a user out after let's say 5 tries. I know some of the better forums do this, I just can't seem to find a good method. Unless I'm simply not entering the correct keywords in Google...

A couple of tests I did had the session variable storing a 'login_attempt' counter that defaults to 1 when the login form is loaded. Upon each failure, regardless of username given (doesn't have to actually be a valid user, in other words), the counter is incremented. Once it hits 5, it trips a session variable 'lockout' to the current time and the user is locked from any attempts for 15 minutes. Now, this works, but it's not persistent; the user can simply close the browser and start over again (I haven't tried this with an automated script as of yet, not sure how it would act). Storing a cookie with the 'lockout' time, the cookie can be defeated / deleted easily. I thought about tracking in the database, but what would you track? IP address? We all know those aren't a valid way to track users.

I guess the essence of my question is "how do I throttle and lockout every login attempt vs login attempts on known usernames". Or is my thinking flawed and I should concentrate only on known / valid usernames?

Posted: Mon Jul 24, 2006 10:57 am
by John Cartwright
The only fully realiable way to block brutforcing to block the user is on a database level, since new sessions can be created, cookies erased (or blocked) and IP's spoofed and legitamatly changing frequently during a single session.

1) User tries to login to jcart/jcart
2) Upon each failure increment failure threashold to that specific user in database
3) Cron every couple of minutes to clear any failure threashold 15 minutes past a certain failure count

Simple as that.. I think :wink:
I guess the essence of my question is "how do I throttle and lockout every login attempt vs login attempts on known usernames". Or is my thinking flawed and I should concentrate only on known / valid usernames?
Giving the attacker as little information as possible is always a good idea, although you don't want to hinder usability of the legitamate users. Perhaps mixing database level locking and sessions to trick attackers into thinking their trying to break real accounts would slow them down furthur.

Posted: Mon Jul 24, 2006 11:11 am
by Chris Corbyn
Jcart wrote:Giving the attacker as little information as possible is always a good idea, although you don't want to hinder usability of the legitamate users. Perhaps mixing database level locking and sessions to trick attackers into thinking their trying to break real accounts would slow them down furthur.
Yeah this is a bit of catch-22 situation. Do you give errors like "Unknown Username" and "Unknown Password" or do you simply say login failed? I prefer the latter.

Honeypots. Hmm.. :) If you have a system with varying access levels you could create literally hundreds of spoof accounts with really easy to guess passwords (as per bot method of breaking in) that go nowhere. Keep a track of such suspicious activity and look into how easy it would be to block the attacker. You can spoof 404 errors for this sort of thing too.

Posted: Mon Jul 24, 2006 11:12 am
by mu-ziq
The way I do mine is every 'User' row has 2 fields called something like 'bad_login_count' and 'last_bad_login_date'. Every bad login to that specific username increments bad_login_count and updates last_bad_login_date with current time. After somebody tried to login incorrectly to a specific existing account say 10 times, before verifying their next login I display a CAPTCHA image and html text field they have to enter. 30 minutes after last incorrect attempt I simply treat bad_login_count as 0, this can also be accomplished by a cron job.

In my personal opinion there is no harm trying to let someone guess the password of a non-existent user, it's the ones in your database that should be protected at the very least from an automated dictionary attack. You do have to balance usability with security but a long enough captcha may prevent most people from trying too many words. You may step it up and add an ip_address restriction or temporary lockout users. But ip addresses change so there may be people who use your site who will be blocked out for no reason because previous ip owner attempted too many incorrect logins. In the case of lockouts, people could purposely lock others accounts by supplying incorrect password.

Having said that, it all depends on the type of application of course. If you are designing a bank system where primary username may be a long hard-to-guess debit card number, a lockout option may be a good option. If you are designing a forum where usernames will be visible then perhaps your legitimate users may suffer more than those who you are trying to keep out.

Posted: Mon Jul 24, 2006 11:18 am
by RobertGonzalez
You could use a table to store bad attempts. Once a user attempts a login, if the user name is in the DB, but the attempt failed, increment their failed_attempts field in their row of the DB. If the user name is not in the DB, check it against the bad attempt table for the user name. If it is in the bad attempt table, increment the user name there. If it is not in the table, add it to the table and increment from there. Purge your bad attempts after a set time so as not to <span style='color:blue' title='I'm naughty, are you naughty?'>smurf</span> off legitimate users trying to get in.

Just an idea. I have no idea if this will suit you or not.

Posted: Mon Jul 24, 2006 11:42 am
by choppsta
The first goal should be to insure that guessing login details is as difficult as possible whilst still being relatively straight forward for users. I usually insist that usernames are unique and that passwords are above a certain length and contain letters AND numbers.

When a failed login attempt is made I log the details in a session data table. When a login is attempted I check the table and count the number of failures, in total, by username and by ip address, over a certain time period, say, the last 45 minutes. If the number of failures for an ip or username is over a certain amount then the login fails and all the user sees is a "login failed" message. To be extra paranoid, if there are, say, 100 failures in total, regardless of who or what details they supplied, within this 45 minute period then NO ONE gets in.

If someone is trying to brute force entry, the fact they can only make a certain number of login attempts every 45 minutes will seriously slow down the time it will take them to get in, to the point where it's unlikely that they ever will.

I think the key is to understanding what the "normal" usage patterns are going to be for your system and then trying to code to recognise "abnormal" behaviour. These can be very different from one system to the next. For example, a private extranet will probably have fair less usage than a public discussion forum.

Posted: Mon Jul 24, 2006 1:58 pm
by bdlang
To all, thanks for your input and ideas; I'll have to have some time this afternoon to digest and rework my strategy, will post back my findings this evening. Now that I've read your responses, I think I'm going to try for a compromise of throttling all logins to 15 seconds as per Shiflett (should defeat an automated script b.f.a.) and locking out / timing out attempts to valid accounts.

Jcart wrote: Giving the attacker as little information as possible is always a good idea, although you don't want to hinder usability of the legitamate users. Perhaps mixing database level locking and sessions to trick attackers into thinking their trying to break real accounts would slow them down furthur.
d11wtq wrote: Do you give errors like "Unknown Username" and "Unknown Password" or do you simply say login failed? I prefer the latter.
I agree; my response to invalid usernames and passwords (based on regex or one of the ctype functions) is 'Invalid username / password' and my response to a failed login is 'Could not log you in, please try again'. I suppose the message about invalid input data should be changed, it might be misconstrued as 'wrong username / password, keep trying till you find one'.