Brute-force/DoS prevention in PHP [closed]
Asked Answered
K

5

17

I am trying to write a script to prevent brute-force login attempts in a website I'm building. The logic goes something like this:

  1. User sends login information.
  2. Check if username and password is correct
    • If Yes, let them in.
    • If No, record a failed attempt in the database. Check if there's too many fails within a given timeframe (eg: 5 in 5 minutes):
      • If Yes, then pause execution for 10 seconds: sleep(10), then report a login failure to the user.
      • Report a login failure to the user immediately

Explaining this to a co-worker, I was asked how this would help if a hacker sent, say, 1000 requests in one second. Would the first 5 would return immediately, and then the remaining 995 all take only 10 seconds?

I have a sneaking suspicion that I don't fully understand how HTTP works - is that situation above even possible, or is there a limit to the number of concurrent requests that a server will handle from one client?

Would a better solution be to have an increasing sleep time?

sleep($numRequestsInLast5Minutes - 5)

So the first 5 would be fast, and then every subsequent one would increase the sleep.

Krystynakshatriya answered 13/11, 2009 at 5:37 Comment(1)
Instead of introducing a sleep, why not introduce a captcha after a maximum allowed failed login attempts?Bleed
I
16

The problem is the balance between user accessibility and attacker model.

First Solution

If not password correct for a certain number of time:
    block the user
    send a reset link to the user

User: could be blocked, and they don't like to reset
Attacker: blocked all users by trying to authenticate to all users (especially if all logins are publicly available)

Second solution

If not password correct:
    sleep(amount_of_time)

The question is: what is the value of 'amount_of_time' ?

User: can be annoying to wait 'amount_of_time' for each error
Attacker: keep trying, with lower test / seconds

Third Solution

If not password correct:
    sleep(amount_of_time)
    amount_of_time = amount_of_time * 2

User: less annoying for few password mistakes
Attacker: block the user to connect by sending lot of incorrect password

Fourth Solution

If not password correct for a certain number of time:
    submit a CAPTCHA

User: need to resolve the CAPTCHA (not too complex)
Attacker: need to resolve the CAPTCHA (must be complex)

Good solution (and used by a lot of sites) but be careful to our CAPTCHA. implementation. Anyway there is a trick (see next solution).

Fifth Solution

If not password correct for a certain number of time:
    block the IP
    (eventually) send a reset link

User: User may be blocked because he cannot correctly remember his password.
Attacker: trying the same password with different user, because blocking is based on number of login by user.

Final Solution ?

If several login attempts failed whatever is the user by an IP :
    print a CAPTCHA for this IP

User: User cannot be IP blocked but must remember its password.
Attacker: difficult to have an efficient brute-force attack.

The important notes

Is the login form or the login submit link which is blocked ? Blocking the login form is useless.

Resistance to brute-force is FIRST a problem of password complexity, so you need a strict password policy (especially in the case of distributed brute force).

I don't mention the fact to hash your passwords with salt, you're already doing this right ? Because if it is easier to access to the password database than brute-forcing, the attacker will choose this solution ("A chain is only as strong as its weakest link").

Incorrigible answered 4/4, 2013 at 11:52 Comment(6)
why hasn't this been upvoted yet? Probably the best answer here.. thx. By mentioning sleep I hope you suggest just blocking access to login? Cause using sleep in php would just slow the system if there are many requests.Awl
About sleep, i was refering to halt script execution before replying to the user request. So I don't think sleep can increase the system charge...Incorrigible
More precisely, sleep() is local to the user request treatement, so there is no impact on the other requests...Incorrigible
but all the users requests are handled by the server ..?!Awl
My point of view was generic (I'm more confident in Java/Servlet/JSP than in PHP), so the sleep() is excepting an interruption of the user request and NOT blocking the whole PHP interpretor. I know it is possible in Java (each user request is dedicated to a thread) but PHP is not multithread and the sleep help page on php.net is not very clear about the real behavior, so I'm not really sure how to handle a sleep to block only one user request.Incorrigible
From my logs, the attacker may have different IP addresses and use them to continuous brute-force attack. One has been blocked then use another one and another one... I think block by IP is no longer work for today. In some case, an attacker may use simultaneous login attempts (try to login at the same time) from 2 or more zombies.Keifer
T
13

I would suggest if the user has tried unsuccessfully, say more than five times and five minutes, you start returning a 503 Service Unavailable immediately, for that IP address. When a login fails, you could use memcache to get the current bad attempts for an IP, and then increment the amount, and save it back to memcache with a 5 minute expiry.

You don't want to put a sleep in your PHP code, as that will allow a single user to create lots of connections to your web server, and potentially bring down other users.

Since the user hasn't logged in, you don't have a session cookie, and if the user is trying to brute force their way into an account, they may not present a cookie at all.

Towrey answered 13/11, 2009 at 5:58 Comment(3)
sleep "...will allow a single user to create lots of connections". Do you have any sources for that? It's not that I don't believe you, I just would like to read some more about it.Krystynakshatriya
@nickf: well, you don't really need to read a research paper to understand the predicament of using sleep() in this situation. Using sleep() basically pauses the execution of the script, meaning the connection is still open until it continues running. There are limits on how many concurrent connections your server can allow so with sleep() you technically make it easier for hackers to DoS your server ...Tenatenable
why not reroute the ipnr through .htaccess?Sham
V
4

I have used something like this...

  1. Check username and password

    1.1 If no match then, record last failed login time for that combo and number of failed logins.

    1.2 Each fail makes the wait between being able to login something like failsCount * 30 seconds, up to a maximum (such as 10 minutes).

  • This means a brute force attack will exponentially take longer and longer.
  • It could lock a user out - but it will not count a failed login whilst trying to login during the lockout period. This should minimise it.

I've developed this but not released it into the wild yet, so any feedback would be appreciated.

Venettavenezia answered 17/11, 2009 at 4:36 Comment(4)
do the failed logins expire? that is, if 2 months ago I forgot my password and tried 6 different things, and now I typed the wrong password, would I be forced to wait 3 minutes before trying again?Krystynakshatriya
Umm I think I've done something like if (timeNow - timeFailed) > 3600 then reset count.Venettavenezia
Nice DoS : an attacker can keep trying false passwords for an user, to forbid him to access.Incorrigible
@Incorrigible Yes, I believe that's better than eventually allowing the attacker access to their account.Venettavenezia
V
2

I am not sure what the best practice is, but when dealing with DoS attacks, a better strategy is to actually divert traffic away from your server. Setting timeouts won't actually help because you're still processing the request and running PHP.

Have you considered setting up another web server running a simpler stripped down version of your login page? When the user tries too many times (e.g., thousands of times), send a message to configure your router and redirect this user to the second web server.

It's like when websites get hit with the slashdot effect, many of them just redirect traffic away until traffic is reduced.

Version answered 13/11, 2009 at 5:49 Comment(1)
IMHO, It depends of the motivation of the attacker: if the main goal is light DOS by one user yes it is a possible counter-measure. But if the goal is to avoid brute force of the password, it depends of the behavior of the second web server (dummy ? blocking ?).Incorrigible
S
2

I made a class that takes care of brute force attack protection in PHP.

https://github.com/ejfrancis/BruteForceBlocker

it logs all failed logins site-wide in a db table, and if the number of failed logins in the last 10 minutes (or whatever time frame you choose) is over a set limit, it enforces a time delay and/or a captcha requirement before logging in again.

example:

//build throttle settings array. (# recent failed logins => response).

$throttle_settings = [
    50 => 2,            //delay in seconds
    150 => 4,           //delay in seconds
    300 => 'captcha'    //captcha 
];
 
$BFBresponse = BruteForceBlocker::getLoginStatus($throttle_settings); 

//$throttle_settings is an optional parameter. if it's not included,the default settings array in BruteForceBlocker.php will be used

switch ($BFBresponse['status']){

    case 'safe':
        //safe to login
        break;
    case 'error':
        //error occured. get message
        $error_message = $BFBresponse['message'];
        break;
    case 'delay':
        //time delay required before next login
        $remaining_delay_in_seconds = $BFBresponse['message'];
        break;
    case 'captcha':
        //captcha required
        break;
}
Separate answered 14/6, 2014 at 22:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.