35

I have my login page and of course I want to prevent brute force attacks and cause less delay for the users when they are logging in.

Currently, you type in your username and password to log in.

I am considering implementing a reCAPTCHA. However, this shows on login after 3 failed attempts.

My question is:

  1. What do you base the attempt on. IP addresses? It can always be hidden... username? What if they're trying a user that doesn't exist?

  2. What would be the best method to count the failed login attempts?

Lemmings19
  • 1,002
  • 2
  • 17
  • 32
lecardo
  • 1,160
  • 3
  • 13
  • 32

3 Answers3

51

Sessions are unreliable because they rely on cookies, CAPTCHAs are regularly broken [including ReCAPTCHA]. The only reliable method is deceptively simple: ask a question. Don't use a math question because computers are surprisingly adept at solving those for some reason. Great old standbys are things like:

  • What is the fourth word in the sixth paragraph on this page?
  • What is the name of the author of this site? [hint]

This is stupid-easy to implement, and very difficult for a machine to solve.

As for bute-forcing, try adding two fields to your user table, 'first_failed_login' [INTEGER unix timestamp or DATETIME] and 'failed_login_count'. [INTEGER]

<?php
$bad_login_limit = 3;
$lockout_time = 600;

$first_failed_login, failed_login_count; // retrieve from DB

if(
    ($failed_login_count >= $bad_login_limit)
    &&
    (time() - $first_failed_login < $lockout_time)
) {
  echo "You are currently locked out.";
  exit; // or return, or whatever.
} else if( /* login is invalid */ ) {
  if( time() - $first_failed_login > $lockout_time ) {
    // first unsuccessful login since $lockout_time on the last one expired
    $first_failed_login = time(); // commit to DB
    $failed_login_count = 1; // commit to db
  } else {
    $failed_login_count++; // commit to db.
  }
  exit; // or return, or whatever.
} else {
  // user is not currently locked out, and the login is valid.
  // do stuff
}

This will make your login system recognize only 3 login attempts per user every 10 minutes.

Sammitch
  • 25,490
  • 6
  • 42
  • 70
  • 10
    Good answer, but this is only relevant if the hacker is attempting on a user who already exists and it will also lock out the genuine user while the hacker is attempting to brute force him. – dlofrodloh Oct 25 '14 at 13:04
  • 7
    @user2721465 1. How exactly do you bruteforce a user that does not exist? 2. How do you distinguish a genuine user from an impostor if not with a password? It is possible to allow the genuine user to unlock their account out-of-band with an email or something, but that is beyond the scope of the question. – Sammitch Oct 27 '14 at 18:10
  • 4
    @Sammitch this mechanism will allow an attacker to repeatedly log in as he attempts to find a genuine user account, and he will know when he finds one because the brute-force measures only occur when attempting to log in as a genuine user. – reformed Apr 12 '17 at 18:42
  • 2
    @dlofrodloh and reformed was right. Attacker can notice the genuine user. However this is still good answer "if" you combine with this method of protection using cache. ( https://coderwall.com/p/sauviq/brute-force-protection-in-php ) – vee Jun 10 '17 at 14:36
  • I bind the `failed_login_count` and `first_failed_login` to the client **IP**, right ? – Accountant م May 02 '18 at 13:38
  • I mean if the attacker knows the email/username of a user he can block him using this system. Why shouldn't the `failed_login_count` and `first_failed_login` be in the `IP` table not the `user` , and the block is done to the IP? – Accountant م May 08 '18 at 22:03
  • 1
    @Accountantم because then all you need to do is use multiple IP addresses and the protection is meaningless. – Sammitch May 09 '18 at 17:56
3

Do not rely on sessions or cookies, those trust the client and you should NEVER trust the client. 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;

 }
idmean
  • 13,418
  • 7
  • 47
  • 78
ejfrancis
  • 2,465
  • 3
  • 22
  • 41
2

Try to check that your are dealing with a real browser. Maybe some nasty java script challenges with random function names or something could block a lot of simple scripts, unless they remote control a real browser (which is not that uncommon), or evaluate the js/css correctly in the scraper script.

I would recommend to read further on this topic and test your solution against python mechanize or some other well known scraper tools.

But one is for sure, there is no real solution against automatic attacks.

enthus1ast
  • 1,910
  • 14
  • 18