Jump to content

[PHP] How to block user after 3 fail of submiting wrong information


Go to solution Solved by Jacques1,

Recommended Posts

As title says i need to block user for 30 minutes after he enter invalid data 3 times in a row. I know i need to enter ip and time in database, but how to stop form to be submited.

 

i have this code for count failed attempts, he increment value in session every time submit is pressed

// postavi ili povecaj broj u sessiji ako je Login button aktiviran
if (empty($_SESSION['failed_login'])) 
{
    $_SESSION['failed_login'] = 1;
} 
elseif (isset($_POST['login'])) 
{
    $_SESSION['failed_login']++;
}

// if login fail 3 times
if ($_SESSION['failed_login'] > 3) {
	$error[] = 'U failed to login 3 times ' . $_SESSION['failed_login'];
} 

This is whole login.php

<?php

// protect page from direct access
if (!defined('AUTH')) { die('You are not authorized to see this page !'); }

if ($general->is_logged() === true)
{
	header('Location: index.php');
	exit();
}

$last_login_date = time();
$ip = $general->get_ip();

// postavi ili povecaj broj u sessiji ako je Login button aktiviran
if (empty($_SESSION['failed_login'])) 
{
    $_SESSION['failed_login'] = 1;
} 
elseif (isset($_POST['login'])) 
{
    $_SESSION['failed_login']++;
}

// if login fail 3 times
if ($_SESSION['failed_login'] > 3) {
	$error[] = 'U failed to login 3 times ' . $_SESSION['failed_login'];
}

// login form
if (isset($_POST['login']))
{
	$username = trim($_POST['username']);
	$password = trim($general->safepass($_POST['password']));

	// if user entered username and password
	if (empty($username) || empty($password))
	{
		$error[] = 'Please enter username and password';
	}
	else
	{
		// login query
		$login = $users->login($username, $password);

		// cookie login
		if (isset($_POST['stay_logged']))
		{
			// check if username and password is valid 
			if ($login) 
			{
				$user_id = $login['id'];

				// expire time for cookie 1 month
				$expire = time()+60*60*24*30;

				// make random code for token
				$rand = hash('sha512', mt_rand());

				// set cookies
				setcookie('token', $rand, $expire);
				setcookie('username', $login['username'], $expire);
				setcookie('id', $login['id'], $expire);

				// update user last_login, ip, token code
				$update_login_data = $users->update_cookie_login($last_login_date, $ip, $rand, $user_id);

				// redirect user to index.php and exit script
				header('Location: index.php');
				exit();
			}
			else
			{
				// if username or password is not valid
				$error[] = 'Invalid username or password';
			}
		}

		// session login
		else
		{
			// check if username and password is valid
			if ($login) 
			{
				// make sessions with user_id and username
				$_SESSION['id'] = $login['id'];
				$_SESSION['username'] = $login['username'];
				$user_id = (int)$_SESSION['id'];

				// update user last_login, ip
				$update_login = $users->update_user_ip_login($last_login_date, $ip, $user_id);

				// redirect user to index.php and exit script
				header('Location: index.php');
				exit();
			}
			else
			{
				// if username or password is not valid
				$error[] = 'Invalid username or password';
			}
		}
	}
}

?>

<h3>Log in</h3>

<?php
if (!empty($error)) {
	echo '<div class="big-error-msg"><ul style="margin:0 0 0 20px;">';
	foreach ($error as $error) {
		echo '<li>'.$error. '</li>';
	}
	echo '</ul></div>';
}
?>

<form action="" method="POST" class="login-form">
	<input type="text" name="username" placeholder="Username" required>   
	<input type="password" name="password" placeholder="Password" required><br><br>
	<input type="checkbox" name="stay_logged"><label style="padding:0 0 0 10px;">Remember me ?</label><br>
	<input type="submit" name="login" value="Log in" class="small-button">
	<label>
		<p><a href="index.php?page=forgotten_pass" title="Forgotten password ?">Forgotten password ?</a></p>
		<p>Don't have an account ? <a href="index.php?page=register" title="Register">Register</a></p>
	</label>
</form>

The functionality you are trying to implement is implemented much differently than what you are currently doing. What this is meant to prevent is a malicious user from attempting many password combinations against a username to try an find a match. So, using the session for this doesn't help as the user can simply close their browser to renew the session. You would also not store the IP in the database as that can be easily spoofed.

 

The typical solution is to lock the "User ID" after three failed attempts (not the person who is trying to log in. So, if three unsuccessful attempts are made with the same User ID, then that User ID is locked. It doesn't matter if those three attempts came from the same IP address or different ones. So, when a failed login occurs, simple set a value in the User table for failed logins and increment. Once it hits three, consider the user "locked" and don't process any more login attempts for that user. You can either require that the user get unlocked via a password reset or have it unlock after a certain amount of time (which would require you to also store the last login attempt timestamp). Just don't forget to reset the failed attempts count whenever a successful attempt is made.

This account locking stuff is highly questionable, as popular as it may be.

 

First of all, you now have a gigantic denial-of-service vulnerabity: Anybody can lock out arbitrary users simply by constantly sending wrong passwords. If an attacker does this a couple of times, you'll soon have no users at all.

 

Secondly, this feature creates a false sense of security. If the password is so incredibly weak that it won't even survive a primitive online attack, then it won't survive any attack. The first database leak will immediately reveal it, no matter how well it has been hashed. It makes much more sense to solve the actual problem and educate the users on choosing better passwords.

 

Third, an attacker can easily circumvent the check by trying one password on many accounts instead of many passwords on one account.

 

And last but not least, every single implementation I've seen so far was wrong (including Sinan's link above). They all make the same mistake:

  1. They count the current number of failed login attempts.
  2. Time passes.
  3. They increment or reset the counter.

Well, what if I make hundreds of concurrent requests between step 1 and step 3? Then the script always checks the old counter value and will accept any number of attempts, When the counter is finally incremented, it's already too late.

 

The counter must be incremented and read in a single atomic operation:

UPDATE
	users
SET
	failed_login_attempts = LAST_INSERT_ID(failed_login_attempts + 1)
WHERE
	user_id = 1
;

The new counter value can then be fetched with LAST_INSERT_ID().

 

But again: The whole idea doesn't make a lot of sense. If you absolutely must have this, because your boss or your users demand it, then at least don't lock the account but rather force the user to solve a reCAPTCHA on every subsequent attempt. This also slows down the attack and increases its costs (literally), but it doesn't have this devastating effect on legitimate users.

Look i dont want to lock user_id because its a bit stupid, for example i go and fail 3 time with every username i want and i lock many account, i want to block ip for a certain time so he cant access site again.

 

So system will work like this:

u press login button and u get error invalid username or password, or please fill all fields ( its fail so update ip and Attempt + 1 )

u press login button again u get error -> check ip and attempts if ip is same update attempt +1 if not same put ip + add attempt + 1

when u got 3 failed attempts lock script so user from that ip cant try data in login form anymore  ( if he press Login script do nothing )

 

And for captcha i didnt see anyone to put captcha on login script.

 

i want to block ip for a certain time so he cant access site again.

 

So, a malicious user could just spoof their IP address and continue with whatever they were doing. Plus, many users could be behind the same external IP (NAT). So the actions of one user would lock out all of those users.

Look i dont want to lock user_id because its a bit stupid, for example i go and fail 3 time with every username i want and i lock many account, i want to block ip for a certain time so he cant access site again.

 

This is even stupider. Look, there's no absolutely no shortage of usable IP addresses. Even the dumbest script kiddie knows how to reset their router, switch to a different HTTP proxy, use Tor or whatever. Now they got a fresh IP and can start over. And more advanced attackers have large botnets with thousands of zombie PCs. 

 

So this IP blocking thingy does absolutely nothing. Even worse: While you fill your IP blacklist, you increase the risk of accidentally hitting a legitimate proxy and locking out legitimate users.

 

 

 

So system will work like this:

u press login button and u get error invalid username or password, or please fill all fields ( its fail so update ip and Attempt + 1 )

u press login button again u get error -> check ip and attempts if ip is same update attempt +1 if not same put ip + add attempt + 1

when u got 3 failed attempts lock script so user from that ip cant try data in login form anymore  ( if he press Login script do nothing )

 

This is exactly the mistake I've described above when I talked about broken implementations.

 

 

 

And for captcha i didnt see anyone to put captcha on login script.

 

Then you haven't seen a lot of websites. But why does it matter, anyway? Shouldn't you worry about the effectiveness of a method rather than its popularity?
 
I mean, you can do whatever you want. If you think that half-working IP blocks are totally great, go ahead. But in my opinion, this is bullshit.

But what he have from spoofing ip when that is using only for check login attempts ? And it have 1 table in database just for checking that 3 fields, ip, time, attemps. And it will be locked for 15 mins max. ip and attempts will remove after it

Edited by mlukac89

The point is that your IP checks are useless if the attacker simply keeps switching to new addresses.

 

You're somehow assuming that everybody is limited to a single IP address. This is not the case. There are plenty of public proxy servers, there's Tor, there are botnets, there are Internet service providers which give their customers a fresh IP whenever they reconnect. Each of this provides an attacker with a large pool of different IP addresses they can use. Like I said, there's no shortage.

 

So what exactly is the point of blocking one particular IP address if the attacker can simply switch to a different one? Even better for them: They can reuse all blocked addresses after 15 minutes.

 

Combined with the various implementation errors I've already pointed out, this entire feature is useless. It may sound like a good idea, but it does absolutely nothing.

  • Solution

Count the failed login attempts per user with the LAST_INSERT_ID() query above. Once the counter has reached a certain limit, the user must solve a CAPTCHA on every subsequent attempt.

 

In addition to that, you should monitor the total number of failed logins. If this number gets exceptionally high, force all users to solve a CAPTCHA.

 

But most importantly: Educate your users on choosing good passwords and using password managers. All login blocks are really just a band-aid to make weak passwords survive a little bit longer. They do not solve the underlying problem. The only actual counter-measure against brute-force attacks is to have a reasonably strong password.

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.