views:

111

answers:

4

I am having trouble searching for what I am looking for. I figure it is best I ask here, so I can also find out what is the best practice or method for what I am attempting.

I want to make a lockout script that prevents people from trying to login in too many times to prevent password cracking. I have one that makes a fade-in pop-up, which creates a slight delay, but to prevent spamming and JavaScript being turned off, I want a more persistent way of preventing someone from trying to login in too many times. I thought session variables would be best for this, but I have no idea how to "time" it.

Can anyone help? I'm using PHP and JavaScript (with jQuery).

A: 

Depends on what you want to achieve you can use either session variables (as Rich Adams proposes) or save login attempts in database (if you want, for example, temporary block an account after entering an incorrect password few times)

a1ex07
A: 

First of all using session to prevent people from brute-forcing password is a bad idea. Session rely on cookie and if you just omit to send cookie each time you make an attempt the server will create a new session each time and you won't be able to stop anything no mater the script you are using.

What you could do is the following, in your database containing the users data, add a field named lastAttempt containing the time at witch the last attempt was made for that user. If the delay between the last attempt is too short you display an error if the delay was ok, just update it.

HoLyVieR
+1  A: 

Session variables have a flaw in that they just have to deny cookies and then they are exempt.

You would be better off creating a table for this in a database with the userid.

create table failedlogins (
    id INT NOT NULL auto_increment,
    user_id INT NOT NULL,
    time_tried DATETIME NOT NULL,
    primary key(id),
    index(user_id));

Then you just insert a record on a failed attempt. Then, when the user attempts to login you check "loginLocked($user);" which would query that table for the current time - x minutes (where x minutes is the time you want spaced through x attempts) So say 3 attempts allowed per 5 minutes:

function loginLocked($user) {
    $query = "SELECT count(fl.id) FROM failedlogins fl 
                 JOIN user u ON fl.user_id = u.id
                 WHERE time_tried < DATE_SUB(NOW(), INTERVAL 5 MINUTE) AND u.username = '" . mysql_real_escape_string($user) . "'";

    $res = mysql_query($query) or trigger_error("Failed Login Query failed: " . mysql_error();

    $attempts = mysql_result($res, 0, 0);

    return ($res < 3);
}

That assumes the tables etc. But should give you a good start / idea on how to handle it. You will have to insert an entry each time a login fails.

EDIT:

My SQL Time check may be wrong, as I do not have this setup and I wrote it on the spot, that might be an area that needs adjusting.

Added the user to the where clause, sorry forgot about that.

Clarification: Yea, Locked should be more or less a (re)Captcha is now required to access the account on it's next login attempt.

Brad F Jacobs
Well, it could be a valid user trying. Perhaps a 15 minute timeout might be better. But yea, you could definitely secure it more by something like if a user has 10 tries in a day lock them out for administrator approval.
Brad F Jacobs
If the "user" has not logged in, how do you get the UserID?
Scott Saunders
@The Rook 5 minutes might not seems a lot, but if you consider that you are limiting brute forcer to 3 attempt per 5 minutes, it slows down the brute force process by quite a lot.
HoLyVieR
Nice approach (and better than using session variables), but what do you do if a robot tries a common password with lots of user names?
Marcel Korpel
@Marcel Korpel ooah dude good call. Yeah what if he has a list of every user name, and just trys `password` for each user name? I took my +1 back, thats really bad.
Rook
@Scott Thanks for pointing that out. Had the join, but no user in the where clause. @Marcel, well that could be an issue, so yea. Logging an IP might help, but if it is a bot, chances are it can change IP's too. Hard to say.
Brad F Jacobs
@premiso my problem is that it has to be ip based. In many apps its pretty easy to get a full list of user names for instance you could iterate over a url like this: `http://localhost/profile.php?user_id=1`
Rook
@premiso there is also a DoS condition, lets say if you want to keep in the admin from logging in. you could just keep bruteforcing his account :)
Rook
Yea, I get that point. Perhaps there needs to a solution that incorperates the IP, but again, the IP is not a sure fire way to ban someone, given schools / work / home accounts even. Perhaps a good solution is to have a secret question / answer for the user and if 1 failed attempt is reached they have to answer that to try again. Still it subcumbs to the same flaw as before.
Brad F Jacobs
@premiso Don't lock, just prompt them with a catpcha. You care if they are bot, thats all.
Rook
So I'm not sure if this is getting positive or negative reception ... either way, could you break down your query please? A lot of new stuff I've never seen before.
Tarik
+3  A: 

First of all, don't lock an account, if someone hits the cap prompt them with reCaptcha.

You cannot use the $_SESSION variable for this because this is bound to a cookie value. If someone is brute forcing the username/password then they can just get a new cookie, and a new $_SESSION. You MUST use a database for this. for every failed login you should make a entry in a simple database with at least two columns ip, timestamp. You should have timestamp set to the current time on insert. When someone logs in you should look

select count(ip) from brute_force_protection where DATE_SUB(NOW(),INTERVAL 1 DAY)>=timestamp and ip='".$_SERVER['remote_addr']."'

Count up the number of cases, if its more than 3, then ban that ip. You could do an mysql_real_escape_string() around the remote_addr, but in all reality this value is pulled directly from apache's TCP socket and the attacker cannot control this value unless he can poison your variable name space (which could be done with extract()).

EDIT:
You could combine this approach with premiso's and lock ip addresses and accounts to produce a very solid system. Where in you can protect against attackers with a list of proxy servers or a botnet, and you can protect against someone trying the same password for all user accounts.

Rook
This is a good approach, but if it is a bot doing the checking, could they not easily just switch IP's every third try? Also what happens if they are at school / work and their co worker or school mates use the same site and 4 people each miss 1 password attempt? I know all the spam bots that I block on my site switch IP's constantly. Just something extra to think about.
Brad F Jacobs
@premiso You *could* use a proxy server and switch that way, keep in mind that spoofing TCP is impossible because of the three way handshake. However, this does raise the bar, where as your approach does not raise the bar. If both of our approaches where combined, in that ip addresses are locked out for a day and individual accounts where also locked, then you'd have really solid system.
Rook
Yep a combination of the two would be a great start.
Brad F Jacobs
The IP address is the only identity that a non-logged-in user has. You will not be able to reliably determine whether login attempts from different IP addresses are the same person or bot or not. You may often have real users attempting to login and mistyping their passwords while a bot runs. A brute force attack that is limited to three tries every five minutes will only be worthwhile if you are a very high value target.
Scott Saunders
I suggest that you do NOT try to combine this with a user based system. Unless you have a valid username-password combination, you do not have a user. You have no way to determine if someone entering incorrect credentials is a user of your system or not, so you should simply limit future attempts from that IP address. You should NOT screw around with the user accounts that match the username they've entered because it's probably not that user.
Scott Saunders
Well since you suggest against this, what is your suggestion? Care to enlighten us?
Brad F Jacobs
@Scott Saunders It doesn't matter if its not that user, so what if a random person has to solve a capthca. There is a condition where you could spill usernames, although you could pretend like users exist... There is also a trade off for protecting against proxy servers. I guess you have to decide the lesser of two evils. I think that proxies is more evil than spilling usernames, mostly because many applications spill usernames anyway.
Rook
I think this answer, up to the EDIT is the way to go: limit login attempts by IP address. A non-logged in user should receive no indication of whether they entered a correct username or password - just that the combination is correct and they are logged in, or that the combination was not and they must try again (with some delay after their limit is reached). If you're really concerned with bots, go ahead and use recaptcha on every login. I agree that a captcha isn't a big deal if the site will still be a bot target even with the login limit.
Scott Saunders
I will play with this and try to implement it. The database query is kind of confusing because I'm relatively new. How would I begin to combine yours and premiso's advice? They both seem kind of similar, even though the code is different.
Tarik
@Tarik i don't see how they are similar. One is ip based and the other is user based. I'm not sure what you are asking. You have a lot of the code already, i think you need to learn more.
Rook