7

I'd like to be able to throttle login attempts based on failed attempts but I got some questions.

Should I use MySQL? (read that it could strain the DB)
Should I throttle per user and system-wide or just system-wide? (so to stop normal people from guessing passwords)
How should I calculate my threshold? (so it automatically adapts to changes/growth)
How should I retrieve this threshold? Query/calculate on every fail or store on cache?
What should I use to throttle? (read a response that sleep() could end up straining the server)

Does anybody have some sample code?

I'm quite new at this so I appreciate the help! Thanks

RS7
  • 2,293
  • 7
  • 31
  • 53

3 Answers3

6

I implemented a poor-man's throttling mechanism in phunction using APC alone, this is how I use it:

// allow 60 requests every 30 seconds
// each request counts as 1 (expensive operations can use higher values)
// keep track of IPs by REMOTE_ADDR (ignore others)

$throttle = ph()->Throttle($ttl = 30, $exit = 60, $count = 1, $proxy = false);

if ($throttle === true)
{
    // IP exceded 30 requests in the last 60 seconds, die() here
}

else
{
    // $throttle is a float
    // number of requests in the last 30 seconds / 30 seconds

    /*
     1 req / 30 = 0,033 sec
     5 req / 30 = 0,166 sec
    10 req / 30 = 0,333 sec
    15 req / 30 = 0,5   sec
    20 req / 30 = 0,666 sec
    25 req / 30 = 0,833 sec
    30 req / 30 = 1     sec
    */

    usleep(intval(floatval($throttle) * 1000000));
}

I use this on my Front-Controller and pass the value to my routing method, but that's another story.

The bottom line is that if you use APC you're able to keep things very fast in memory and with little memory consumption because APC follows a FILO methodology. If you need way higher timeouts you may consider using something that's not memory based though.

BTW: MySQL supports tables with the MEMORY engine.


The problem with sleep():

A typical Apache web server with PHP installed as a module will eat about 10 MB of RAM per instance, to avoid exceeding your available ram there are some Apache settings that you can configure to limit the maximum number of instances that Apache is able to start.

The problem is when you sleep(), that instance is still active and with enough requests could end up eating all the available slots to start new servers, thus rendering your web site inaccessible until some pending requests are completed.

There is no way to overcome this from PHP AFAIK, so in the end it's up to you.


The principle is the same for system wide throttling:

function systemWide($ttl = 86400, $exit = 360)
{
    if (extension_loaded('apc') === true)
    {
        $key = array(__FUNCTION__);

        if (apc_exists(__FUNCTION__) !== true)
        {
            apc_store(__FUNCTION__, 0, $ttl);
        }

        $result = apc_inc(__FUNCTION__, 1);

        if ($result < $exit)
        {
            return ($result / $ttl);
        }

        return true;
    }

    return false;
}
Alix Axel
  • 141,486
  • 84
  • 375
  • 483
  • From what I understand this is for per user throttling yes? If so, how do you suggest I implement a system-wide throttle to prevent distributed attacks? – RS7 Feb 18 '11 at 03:16
  • 2
    The problem with this is that even though you're not eating CPU time with the 1 second sleep, you're still tying up the PHP process for that long. And with most front-end servers that will rapidly degenerate into bringing down your whole server (similar to the slashdot effect, but with one user instead of tons)... – ircmaxell Feb 18 '11 at 03:19
  • @ircmaxell: I know, but you only `sleep()` if you want too. – Alix Axel Feb 18 '11 at 03:24
  • @RS7: This is per-IP throttling. I'm not exactly sure what you mean by system-wide throttling but if I get what you're saying a single key incremented with `apc_inc()` would do the job for the whole system. – Alix Axel Feb 18 '11 at 03:27
  • @ircmaxell: Could you suggest me something? Thanks – RS7 Feb 18 '11 at 03:28
  • @alix: Thank your for your help so far! What I mean by system-wide is that I'd be checking for brute force attacks not just by one person, with one IP, but also checking for distributed brute force attacks, with varying IPs. This way, even if they use many IPs and space out requests per IP, I'd be able to prevent or complicate the attack. It is [something I read](http://stackoverflow.com/questions/549/the-definitive-guide-to-website-authentication/477586#477586) in one of Jens Roland's posts. Thanks again – RS7 Feb 18 '11 at 13:08
2

Log failed login attempts in a table like this:

FailedLogins
id
timestamp
ip

Every time a user tries to log in you check to see if the user's IP address has X number of failed login attempts in the last Y seconds.

If the user has failed X times within Y seconds you present an error message or a CAPTCHA.

1

A MySQL database is able to handler tones of requests / sec, so you don't have to worry about bottle necks if you don't have thousands of users.

You could also use sleep() NOTE: PHP handles more users than ASP.NET - So again. If you don't have thousands of users, you could use sleep methods, without bottle necks.

The way I normally do it, is by storing login attempts (IP, userID and timestamp). Store it in a table, and reset the table when you feel like it (at a certain size or time of day). If a userID + IP has more than "amount of login attempts" in a "certain amount of time", redirect the user to a page that tells the user that he/she has used to many attempts and won't be able to login the next 15min (or whatever you feel like). A bit "Windows" like I guess, but it works like a charm :)

hogni89
  • 1,852
  • 5
  • 21
  • 38
  • @user622470: The problem with MySQL is that he doesn't need to have thousands of users, only one that knows how to make thousands of connections really fast. – Alix Axel Feb 18 '11 at 03:41
  • 1
    Your attacker doesn't have access to your MySQL, only your web page. – hogni89 Feb 18 '11 at 19:02