views:

1025

answers:

10

I was just reading this post http://stackoverflow.com/questions/549/the-definitive-guide-to-website-authentication-beta#477585 on Preventing Rapid-Fire Login Attempts.

Best practice #1: A short time delay that increases with the number of failed attempts, like:

1 failed attempt = no delay
2 failed attempts = 2 sec delay
3 failed attempts = 4 sec delay
4 failed attempts = 8 sec delay
5 failed attempts = 16 sec delay
etc.

DoS attacking this scheme would be very impractical, but on the other hand, potentially devastating, since the delay increases exponentially.

I am curious how I could implement something like this for my login system in PHP?

+2  A: 
session_start();
$_SESSION['hit'] += 1; // Only Increase on Failed Attempts
$delays = array(1=>0, 2=>2, 3=>4, 4=>8, 5=>16); // Array of # of Attempts => Secs

sleep($delays[$_SESSION['hit']]); // Sleep for that Duration.

or as suggested by Cyro:

sleep(2 ^ (intval($_SESSION['hit']) - 1));

It's a bit rough, but the basic components are there. If you refresh this page, each time you refresh the delay will get longer.

You could also keep the counts in a database, where you check the number of failed attempts by IP. By using it based on IP and keeping the data on your side, you prevent the user from being able to clear their cookies to stop the delay.

Basically, the beginning code would be:

$count = get_attempts(); // Get the Number of Attempts

sleep(2 ^ (intval($count) - 1));

function get_attempts()
{
    $result = mysql_query("SELECT FROM TABLE WHERE IP=\"".$_SERVER['REMOTE_ADDR']."\"");
    if(mysql_num_rows($result) > 0)
    {
        $array = mysql_fetch_assoc($array);
        return $array['Hits'];
    }
    else
    {
        return 0;
    }
}
Chacha102
You can also use: sleep(2 ^ (intval($_SESSION['hit']) - 1));
Cryo
The obvious problem being that a serious brute force attacker wouldn't bother to actually handle cookies, so the session becomes worthless.
deceze
sleep(2 ^ (intval($count) - 1)); I kind of like the array so I can set the amount of time to wait but I am curious, how does this equate? Also if I were to save to DB, once a user log's in would I delete there hits from the DB so it is a fresh start when they try to login next time after being logged in?
jasondavis
You would set an expire time, as the delay should expire after a certain amount of time. Anything else is up to you. If someone logs in/out and tries to log back in, you might or might not want to keep their past delay timer. Thats your call.
Chacha102
Also remember that Cryo's answer doesn't use the array.
Chacha102
-1 because anyone trying to do this wouldn't enable cookies for their bot.
Lotus Notes
+2  A: 

You can use sessions. Anytime the user fails a login, you increase the value storing the number of attempts. You can figure the required delay from the number of attempts, or you can set the actual time the user is allowed to try again in the session as well.

A more reliable method would be to store the attempts and new-try-time in the database for that particular ipaddress.

Jonathan Sampson
I currently do something like this but I was thinking if there was a DoS attack I wasn't sure if a bot or anything would still work with sessions but I guess it would have to work
jasondavis
The bot can easily choose to ignore the session cookie. Use database with IP and the bot can't do nothin' about it besides switching IP.
Matchu
+1  A: 

I generally create login history and login attempt tables. The attempt table would log username, password, ip address, etc. Query against the table to see if you need to delay. I would recommend blocking completely for attempts greater than 20 in a given time (an hour for example).

sestocker
+3  A: 

Store fail attempts in the database by IP. (Since you have a login system, I assume you know well how to do this.)

Obviously, sessions is a tempting method, but someone really dedicated can quite easily realize that they can simply delete their session cookie on failed attempts in order to circumvent the throttle entirely.

On attempt to log in, fetch how many recent (say, last 15 minutes) login attempts there were, and the time of the latest attempt.

$failed_attempts = 3; // for example
$latest_attempt = 1263874972; // again, for example
$delay_in_seconds = pow(2, $failed_attempts); // that's 2 to the $failed_attempts power
$remaining_delay = time() - $latest_attempt - $delay_in_seconds;
if($remaining_delay > 0) {
    echo "Wait $remaining_delay more seconds, silly!";
}
Matchu
Database is definitely the way to do it. That way you also have a history to look back at as well.
sestocker
I was thinking of something like this, I think vbulletin forums do something like this, session could be reset by closing the browser and coming back as well I think
jasondavis
Can you explain what kind of time this creates pow(2, $failed_attempts) ?
jasondavis
I wouldn't suggest that you use sleep, since it would block that instance of PHP until the sleep finishes. If the attacker would open up a bunch of connections to bruteforce the server, it would very quickly backup with PHP requests. It would be better to fail all login attempts during the "delay" time period for that IP.
Kendall Hopkins
See the PHP docs for pow() - it returns 2 to the power of $failed_attempts :)
Matchu
@SoftwareElves I think that is how the vbulletin forums work, if you fail login and try again too soon, they will print to screen a message saying try again in X amount of seconds
jasondavis
Yeah, I misinterpreted the "delay" concept. Edits coming in...
Matchu
I would cap `$remaining_delay = min(3600, $remaining_delay);`.
Alix Axel
+3  A: 

You have three basic approaches: store session information, store cookie information or store IP information.

If you use session information the end user (attacker) could forcibly invoke new sessions, bypass your tactic, and then login again with no delay. Sessions are pretty simple to implement, simply store the last known login time of the user in a session variable, match it against the current time, and make sure the delay has been long enough.

If you use cookies, the attacker can simply reject the cookies, all in all, this really isn't something viable.

If you track IP addresses you'll need to store login attempts from an IP address somehow, preferably in a database. When a user attempts to log on, simply update your recorded list of IPs. You should purge this table at a reasonable interval, dumping IP addresses that haven't been active in some time. The pitfall (there's always a pitfall), is that some users may end up sharing an IP address, and in boundary conditions your delays may affect users inadvertantly. Since you're tracking failed logins, and only failed logins, this shouldn't cause too much pain.

Mark E
IP addresses are not a good solution:1) they are often shared2) its easy to keep changing the address using TOR
symcbean
@symcbean I've addressed multiple solutions, any combination of which will thwart some attackers, there's no magical solution. That IP adresses are shared is less of an issue, as I discuss in my answer; that someone may change it using TOR seems less likely than someone forcing new sessions. Is there a 4th option I've missed?
Mark E
A: 

As per discussion above, sessions, cookies and IP addresses are not effective - all can be manipulated by the attacker.

If you want to prevent brute force attacks then the only practical solution is to base the number of attempts on the username provided, however note that this allows the attacker to DOS the site by blocking valid users from logging in.

e.g.

$valid=check_auth($_POST['USERNAME'],$_POST['PASSWD']);
$delay=get_delay($_POST['USERNAME'],$valid);

if (!$valid) {
   header("Location: login.php");
   exit;
}
...
function get_delay($username,$authenticated)
{
    $loginfile=SOME_BASE_DIR . md5($username);
    if (@filemtime($loginfile)<time()-8600) {
       // last login was never or over a day ago
       return 0;
    }
    $attempts=(integer)file_get_contents($loginfile);
    $delay=$attempts ? pow(2,$attempts) : 0;
    $next_value=$authenticated ? 0 : $attempts + 1;
    file_put_contents($loginfile, $next_value);
    sleep($delay); // NB this is done regardless if passwd valid
    // you might want to put in your own garbage collection here
 }

Note that as written, this procedure leaks security information - i.e. it will be possible for someone attacking the system to see when a user logs in (the response time for the attackers attempt will drop to 0). You might also tune the algorithm so that the delay is calculated based on the previous delay and the timestamp on the file.

HTH

C.

symcbean
+7  A: 
cballou
This is a good point, I was thinking of this actually since I have seen software that can attempy logins on myspace with email/password files of 100,000 logins and another password of IP address to use, then it could alternate the IP for each request somehow so this would stop things like that I think
jasondavis
It's good to note that the throttle times should be low enough to not annoy normal users but long enough to deter bots from repeatedly firing cURL requests. A user won't even notice a 2 second delay as their next login attempt will likely exceed 2 seconds since the previous attempt. A bot, on the other hand, will be affected greatly by having to wait 2 seconds before another attempt. Script kiddies will likely go elsewhere since a small delay will *greatly* reduce the number of overall requests they can make.
cballou
I like the idea. Maybe you could have a look at this post: http://stackoverflow.com/questions/479233/what-is-the-best-distributed-brute-force-countermeasureIt discusses exactly the same issue (distributed brute force) and it would be nice if you could post your idea in detail there as well
Jens Roland
A: 

IMHO, defense against DOS attacks is better dealt with at the web server level (or maybe even in the network hardware), not in your PHP code.

vicatcu
True, but sometimes you must fight with the stick you have in your hand.
Xeoncross
+1  A: 

Cookies or session-based methods are of course useless in this case. The application has to check the IP address or timestamps (or both) of previous login attempts.

An IP check can be bypassed if the attacker has more than one IP to start his/her requests from and can be troublesome if multiple users connect to your server from the same IP. In the latter case, someone failing login for several times would prevent everyone who shares the same IP from logging in with that username for a certain period of time.

A timestamp check has the same problem as above: everyone can prevent everyone else from logging in a particular account just by trying multiple times. Using a captcha instead of a long wait for the last attempt is probably a good workaround.

The only extra things the login system should prevent are race conditions on the attempt checking function. For example, in the following pseudocode

$time = get_latest_attempt_timestamp($username);
$attempts = get_latest_attempt_number($username);

if (is_valid_request($time, $attempts)) {
    do_login($username, $password);
} else {
    increment_attempt_number($username);
    display_error($attempts);
}

What happens if an attacker sends simultaneous requests to the login page? Probably all the requests would run at the same priority, and chances are that no request gets to the increment_attempt_number instruction before the others are past the 2nd line. So every request gets the same $time and $attempts value and is executed. Preventing this kind of security issues can be difficult for complex applications and involves locking and unlocking some tables/rows of the database, of course slowing the application down.

ellegi
Standard applications run on VPS or share hosts can only handle about 5-30 requests per second. So your method does work, but it is possible 30 attempts might make it before you can block them. Also check your apache logs for stuff like this (post requests especially).
Xeoncross
A: 

I wanna also know how its work

asim