Brute-force/DoS prevention in PHP

nickf picture nickf · Nov 13, 2009 · Viewed 11.8k times · Source

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.

Answer

Kartoch picture Kartoch · Apr 4, 2013

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").