Join Kevin Skoglund for an in-depth discussion in this video Throttling brute-force attacks, part of PHP: Creating Secure Websites.
In this movie, let's talk about how to protect yourself against brute force attacks. A brute force attack is when a hacker systematically tries all possible input combinations until they find the correct solution. Or in this case, the correct password. Imagine that you wanted to break into a combination lock and you tried zero, zero, zero and then zero, zero one and then zero, zero, two and zero, zero three, and you just kept going until you got all the way up to nine, nine, nine. Somewhere in there you would hit the right combination.
That's why it's also referred to as a exhaustive key search. It's like we're looking through all possible keys or solutions and doing it exhaustively, one after another till we find the right one. That might seem like a lot of work for a human to do but computers can do that kind of work very fast. And hackers have another trick up their sleeve. They know that users are more likely to use dictionary words than to use random letters and numbers. So they're going to try those dictionary words first, to see if they can get a match off of any of those.
And that will greatly speed up, their processing time. And that's really what this is about. It's certainly possible to find a solution if you can try every single possibility, but it's a question of time. How long does it take to do that? Is it something that can be done in ten minutes or something that takes 100 years? We're obviously hoping it will be the second one and not the first. There's a formula that describes how much time is required to perform a brute force attack and succeed. That is, the key space raised to the power of the key length times the amount of time it takes to perform each attempt.
Now the key space is the number of possible characters that are allowed in each position. So do we allow only alpha numeric characters or do we also allow symbols as well. Key length is going to be the length of our password. You can see now why I told you previously that we want key space and key length to be as high of a number as we can. Because it will greatly increase the time required. Time per attempt, unfortunately, is a very small number, because computers are very fast and getting faster all the time.
But there are some things that we can do about that as well. So while we can't stop brute force attacks from happening, the best way to manage them is to encourage users to provide strong passwords, to use as many different characters as possible, and to make them as long as possible. Ideally, it'd be great if they weren't dictionary words, but that's a little hard to control. We also want to use slow password-hashing algorithms. Blowfish that we're using, is a good example of that. Now it may not seem that slow to you, but that's because it's only adding about a second to the processing time.
But when a hacker is attempting one million combinations, that one second becomes deadly. Your next offense is logging. Logging the fact that the brute force attacks are happening but logging can also be less passive. It can actually be emailing to administrators, letting them know, that suspicious activity is taking place. And a great way to handle brute force attacks is throttling. Throttling is a way to slow down or lock an account after a certain number of failures. And we can pick a high number like 20, 30 or even 50 so that legitimate users don't trigger it very often.
The account doesn't have to stay locked forever, it can only be locked for five or ten minutes. To a user who has forgotten their password, that's a minor inconvenience. But it's also kind of understandable after 20 or 30 failed attempts. To a brute force attack, this is a major inconvenience, adding days, weeks or even years to the process. And your final tool against brute force attacks is blacklisting the IP address. Ban the IP address from sending any more requests. The problem is that brute force attacks can be distributed across many computers, like botnets.
So you might end up having to blacklist hundreds of IP addresses, but it is the ultimate way to control a hacker's communication with our server. We'll talk more about blacklisting in the next movie. For now, let's look at how we can implement throttling in our sample PHP application. So here I am in Sample App. This is included in the exercise files and it's exactly the same as the sample app we were using in the last movie, except that I've added a new file to it. Throttle_functions.php. Which is in our private functions directory.
And of course, if I'm going to add those functions and want to use them, I need to also add a line to initialize to add require once for those throttle functions. Then I can use them. There are three functions in here. One is record failed log in. That's for recording the log in when the failure happens. We can't count them until we start recording when they happen. Clear failed logins, we are going to use that when the person successfully logs in. We're going to clear out all the old failures. And then throttle failed logins, which will return the number of minutes that they have to wait until they can login again, after they've hit a certain threshold.
Let's look at each one. Record failed logins. It's going to find the user in the fake database and the failed logins table, looking for the username that matches their username. And if it doesn't find a record. It will create a new one and then it will add that to the database. If it does find it, it will update it. The new record that it creates is going to consist of their user name, the number of times that it's failed, the count, and that's going to be one to start with and then the time of their last failure. Which is going to be the current time. And add_record_to_fake_db will take care of adding that record to the database for us.
But if we find an existing record, we're going to update it by adding one to the count, update the time, and we're going to call update_record_in_fake_db and pass in the key that we want it to use with our record, failed_login. That's how it'll know what to match, it'll look for the username, and that's how it will find what record to replace. All right, let's take a look at clear_failed_logins. It looks fairly similar to what we just saw in record_failed_login, except that it sets count equal to zero. So if it finds the failed login, set the count equal to zero.
And then update that record in the database. And then throttle failed logins. This is where we actually decide whether or not we're going to slow the user down. We're going to throttle them at a certain number of attempts. So I've said that it's five here for demonstration purposes. But in real life I think you'd probably pick a higher number. 20, 30, 40, 50, something like that. That would really be something a normal user wouldn't hit that often, but that a hacker doing a brute force attack would hit. I don't want us to go that long until we find a failure so I'm going to set it at five.
Then we've set the delay in minutes to ten. The delay is going to be defined as ten times 60 to get the number of seconds. And then we'll find that failed login in the database. So we'll put it up and see if we find it. If we find it, then we're going to check it's count. And we're going to see if it's count is greater than the maximum allowed by throttle_at. If it is, then, we know we need to slow them down. If not, if either we didn't find a log in record there or if the count wasn't high enough, we just return zero. But if we have to calculate a delay, the remaining delay is going to be the last time they logged in plus the number of seconds in the delay, minus the current time.
And then we can convert that into minutes so that we can report that back to the user by dividing it by 60 because it was in seconds before, and calling ceil on it in order to get the ceiling. That's the r, always round up function. And we'll return that value in minutes out of the function. Okay, so I've got these three functions, but I'm not actually using them yet. Let's put them into our login script. The first place that we can use one is after the failed login, because that's the first place we want to start. We want to record_failed_login for the username.
So that will then record that login as being a failure. If they succeed, then, let's just copy this. Then the first thing we're going to do is clear_failed_logins. There we go, it's plural. So clear_failed_logins. So that's what happens when they succeed in logging in. So that's going to start our record keeping going, but we don't have anything that's actually doing the throttling. So to do that we'll come back up here. And right after we make sure that they've given us a username, right, here we're certain that we have one, now we can check it.
So, we'll put a call here to set $throttle_delay equal to throttle_failed_logins. So this is going to return a number to us, this is going to tell us, how long should we delay it? And if the delay is greater than 0, $throttle_delay is greater than zero, then we'll know that this request is being throttled. If not, then we'll go ahead and do everything we were going to do anyway. Let's just take all of this. We'll indent it. And close our curly brace right here.
All right? You can fold this up if it makes it easier to see where things are. If throttle_delay, and then we'll actually give them just a message here about the throttling. So throttled at the moment, try again after the delay period. And we'll tell them what the delay is. So if the delay is greater than zero, you're being throttled. Otherwise, we're going to go ahead and proceed with the rest. We're going to search the database to retrieve the user data and so on. All right now that we've got our throttling in place, let's try it out in Firefox. Open up Firefox. And I'll just go to my regular log in page.
I'll just reload the page to start with. And then let's log in. I'm going to need to provide both the username and password to get past those validation's that require a presence. So let's do that, kskogland. And then for the password. I'll just type in some gibberish and hit log in. Username password combination not found. Look down here. In failed log ins, we now have a new failed log in for kskogland, the count is one, and this is the time as represented by PHP. It's actually the amount of seconds that it uses. Let's try another one. I'll type more gibberish, log in.
Look down here. Count has changed to two. Let's try it a few more times. Log in count has changed to three. Log in, it's now four. One more, now it's five, but the next one will be the one will be at throttled. Too many failed logins, you must wait ten minutes before you can attempt another login. And sure enough, anything that I type here. Will generate that again. Because remember, we're checking it before we even look at the password. So even if I check a correct password, type my password which is secret, it still doesn't get there.
Right? I'm being throttled before I ever even get there. It's the very first thing that's happening. I must wait those ten minutes before I can do anything else. So, this is how you implement throttling. It's really that simple. You make sure you record the failed log in, you then throttle if there have been too many failed log ins and once they successfully log in, then you clear out those failed log ins
- Cross-site scripting (XSS)
- Cross-site request forgery (CSRF)
- SQL injection
- Encrypting and signing cookies
- Session hijacking and fixation
- Securing uploaded files
- User authentication
- Throttling brute-force attacks
- Blacklisting IPs
- Implementing password reset tokens