views:

353

answers:

4

If my website uses a POST form for login, what is a quick and easy way to prevent a rogue client from flooding my web server with POST requests trying to brute force crack my user accounts?

PHP/MySQL/Apache.

A: 

Well, if you know the IP of the source of the login attempt, you could allow it 5 attempts, then make that IP wait through a 5 minute "cool-off" period. If you think 5 minutes is too short, raise it until it's something more suitable (could go as high as 24 hours, or more if you think it's necessary). This might not work as well if they have dozens of coordinated nodes in a botnet.

FrustratedWithFormsDesigner
+1  A: 

One approach is to keep track of the IP address (or even of the first 3 octets of the IP Address) of each request and to add a significant time in responding to (or even to drop) the requests coming from IPs that have had more than x requests in the last y minutes.

This is ineffective (or less effective) against distributed attacks, but otherwise work quite well, for a relatively simple implentation.

For a stronger proptection, one can also blacklist offending IPs (IPs, or again first 3 octets of IP, which have submitted more than say 6 bad attempts in the last 2 minutes or less) by systematically denying access to such IP for a period of say 15 minutes.

mjv
+1  A: 

If your login form sends a passkey/fingerprint with the post request you can have the php code check if that is valid when receiving the post data before you connect to the database to check passwords.

if ($_POST['fingerprint'] == $fingerprint) {
  connect(); //etc
}

The fingerprint is a string variable which is passed from your login page in the post value, but invisible to the user. The code which checks the validity of the login attempt will check the fingerprint against it's own known good one. If they match then it can continue to access the database and compare login credentials etc. As suggested in the comments, it would be even better if it was dynamically generated.

This way a malicious script would need to have the fingerprint to even try to access your site.

The other option would be having a session variable to record the number of tries in a certain period.

Edward Williams
That's a good one, I'd forgotten that trick. Of course, the fingerprints should be dynamic in some way (even just a simple "colour of the day) or else anyone who reads the source for your form will easily overcome the fingerprint scheme.
FrustratedWithFormsDesigner
Yes, that would be best of course. I'm not sure how they can get the php source though without compromising the server. I'm just a student though so, hey.
Edward Williams
By "reads the source" I meant going to the webpage with the form on it, clicking "view source". If the fingerprint is a hidden varible in the form, it would be not so hidden to anyone who cares to read the HTML source. That's why you'd have to have a system to change the fingerprint, so that even if someone does read it from the page source, it would only be valid for a little while. The only way to be safe from bots that scan the page source is a fingerprint that is ALWAYS unique. Just a little bit more challenging. ;)
FrustratedWithFormsDesigner
@Edward Williams: can you elaborate on the way the fingerprint is calculated, because this seem to introduce several operational difficulties: if time-based, prevents legit customers to use a login page older than a few minutes, if ip-based, prevent legit customers behind a gateway with rotating IPs, etc... ?
mjv
It's just a random character string. I've updated the post with more information on the fingerprint. It is used to stop people from running their own scripts against yours. Regarding the IP/proxy issue I think you could just set a limit for a user/$_session of three failed logins in 30 mins. Using $_SESSION['HTTP_USER_AGENT'] combined with the login attempt limit you can ban just one bot/client rather than banning a whole university's proxy server. Shifflet mentions using this for preventing session hijacking but I think it would be usef: http://shiflett.org/articles/session-hijacking
Edward Williams
@Edward: I understand the idea behind the fingerprint, what I do not see is whether it is constant (which makes its reuse by attacker's own script easy), or whether is is calculated, and if so on which basis. As said in my first question, the choice of this basis can be tricky.
mjv
On the basic level you would have an algorithm to calculate a fingerprint/token, store it in a session variable and then send it via POST. Then do the comparison. atk pointed out the fact that there are problems with RNG and seeds and you would need to make sure it was a complex as possible (have multiple seeds - layers). I guess my solution wouldn't be a complete one, but rather add another layer of security to make it harder for a malicious attacker.
Edward Williams
+5  A: 

Preventing brute force cracking is trickier than it may at first seem. The solution will be to combine controls - one single control will not cut the mustard. And remember the goal: you want to slow down a brute force attack to the point where it will either be ineffective, or you can detect it and take action. The second option is generally more effective than than first.

You could use a captcha (this is currently a popular technique) but captchas can often be automatically read, and when they can't be read by a computer, farms of people can be be obtained by paying low waged workers or by using the captcha to protect "free" porn (both techniques have been used).

The advice of others to use a secret value in the form won't really help; an attacker simply has to parse the HTML to find the secret value, and include it in their post. This is pretty simple to automate, so it's not really a good defense. Oh, and if the value turns out to be easily predictable (using a poor or broken PRNG or a bad seed) you're up the creek, again.

Tracking the IP address is okay, but only if you don't support NAT. With NAT, valid users will appear to be duplicates. And remember that attackers can impersonate other systems; a single attack system can use other IP addresses, and even intercept the traffic to that system (ARP poisoning is one good mechanism for this).

You could use a max number of failed timeouts in a given period of time (like 3 within 1 hour). This slows the attacker down, but doesn't necessarily stop them. You might include an automated unlock, but you'll need to do some math, and make sure that the unlock time is actually useful.

Exponential backoff is another useful mechanism. This might be possible to tie to a session (which the attacker doesn't have to return to the server) to the IP address (With breaks with NAT) or to the account (which doesn't account for brute forcing across multiple accounts).

For the other defenses to be useful, you have to have strong passwords. If your passwords are easy to guess (are they in a dictionary? are they short? are they complex?) the attack will succeed. It's a good idea to implement minimum password strength requirements, and an "illegal passwords" dictionary (combined with common character substitutions for that dictionary). Alternatively, you might use a system like OATH, certificate login, or hardware tokens (like RSA's SecurID).

I think it was Burt Kaliski who discussed client puzzles. Basically, you give the client a challenge that's easy for the server, but difficult for the client; the client DoSes itself by wasting its own resources trying to solve the puzzle. The difficulty, here, would be in determining the right complexity for the puzzle. It might, for example, be factoring a large number. Whatevr it is, you'd have to assume the most effecient possible algorithm, and you'd have to be able to handle different performance of different browsers on different machines (potentially slow) while slowing down automated attacks outside of browsers (potentially faster than your javascript). Did I mention that you'd have to implement a solution in JavaScript?

But you're still stuck with an attack that works across multiple accounts. I'm not aware of any publicly used controls that work well against this, unless you can track IP addresses.

Then, you'll want to protect usernames. An attacker who doesn't know usernames (requiring a system that doesn't indicate when usernames are valid) will have to learn both the username and the password, instead of easily confirming a username, then just attacking passwords.

And you'll need to be careful that error messages, and server timing don't give away (in)valid passwords, either.

And when you deal with error messages, make sure that password recovery mechanisms don't give anything away. Even in otherwise good systems, password recovery can blow the whole thing.

But, all that said, the attack is ultimately dependant upon the server's performance. You might simply implement a very slow mechanism for authentication (has to be slow for both valid and invalid authns). An online attack is guaranteed to go no faster than the server can process requests.

Then, you need to detect rute force attacks, so your system needs a good audit trail. But you'll need to be careful not to log too many log messages or you'll open up an easy way to dos the server by filling up disk space. Something like syslog's "the previous message has been recieved 1000 times" message would be good.

Once you're all done designing things, and again when you're done implementing things, you'll want to examine the whole system, and all features of the system, mathematically model it given the current settings and the server's performance and determine the average amount of time it would take an attacker to brute force (a) a single account, and (b) any account (brute forcing across accounts to avoid account-specific controls).

atk