I made a forum post about this and it was suggested that I should write about it here..

About once a week I get on my logs that someone has tried logging in to my site several times in a row, unsuccesfully. Maybe altering the flood-functionality (for example flood_is_allowed()) to allow the admin to set the maximum amount of login attempts per X minutes could be a good solution?

I'm a bit surprised this wasn't already implemented, correct me if I'm wrong.. seems like a good feature to me ;)

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

RobRoy’s picture

Version: 4.7.6 » 6.x-dev

Features go against latest development version. Sounds like a good idea.

Heine’s picture

While this sounds good, it has one caveat: it makes a denial of service against an account (eg uid 1) fairly easy. Introducing a delay of several seconds, perhaps increasing slightly after multiple attempts, before displaying an error message might be a better idea.

Heine’s picture

To add: with delays, one needs to take into account that an attacker can use multiple clients / connections at the same time:
allowing 100 simultaneous, instantenous attemps and a 20 sec delay would limit an attacker to trying 432,000 passwords a day.

grendzy’s picture

I agree tho DOS issue needs to be considered. Perhaps this could be mitigated by banning the individual IP (instead of the entire user account) after the flood limit is exceeded? A large botnet could still overcome this limit... but an attacker with just a small number of machines / IPs would have a harder time.

432 000 seems like too many attempts to allow. A dictionary-based attack would be pretty likely to succeed with that many attempts.

tormu’s picture

Glad to see that this is considered as a needful feature :)
For small sites with just about 20 users it would be sufficient to set that the individual IP can try for example 5 or 10 times per hour. If the user doesn't remember the password in 5 trys, it's already starting to feel like abnormal usage to me. Well, I hope you Drupal gurus get this thing rolling and implemented in the near future :)

Heine’s picture

Discussing something does not indicate endorsement.

For small sites with just about 20 users it would be sufficient to set that the individual IP can try for example 5 or 10 times per hour.

No really, I'm not interested in trading one weak spot for another weak spot (blocking AOL proxies is generally not acceptable).

meba’s picture

Therefore you need to keep the delay against user account, not remote IP.

15 seconds (seems reasonable, 20 seconds is too close to PHP max execution time) delay after every 3 wrong attempts means 5760 attempts a day which is reasonable and something you will likely notice and do another action.

RobRoy’s picture

FYI, this could easily be done as a contrib module so unless there is a great implementation idea and support, it may be better left for contrib.

Gurpartap Singh’s picture

How about a feature request in akismet module or take the bits from there for new module? It already implementing lots of stuff similar to this one.

bjaspan’s picture

This is a tough question.

I like the general idea: after X failed attempts in Y minutes, all login attempts will fail for Z minutes. This is not affected by the fact that an attacker can run simultaneous connections whereas pretty much any "slow down the login process" technique will either have no effect or will tie up extra resources on the server.

However, any such approach also necessarily leaves open a denial of service attack. Of course, just hammering Drupal with requests is a denial attack of its own, and there is nothing Drupal can do to protect itself from botnets, so it isn't clear that adding this additional denial vector is a real change.

One option would be not to enforce the max-failed-attempt rule against uid 1 on the assumption that the admin can be trusted to choose a good password (unlike most users who can't be).

webchick’s picture

Hm. Does it have to be either an IP or a username lockout? Could it not be a mixture of both?

Example:

I am 3vilh4x0r going through proxy.aol.com 1.2.3.4 and attempt to login as the "root" account 4 times in an hour.
I am 1nn0c3nt4ng3l going through proxy.aol.com 1.2.3.4 and attempt to login to the "angel" account.

Drupal blocks 1.2.3.4 from trying to login to the "root" account for an hour.
Drupal does NOT block 1.2.3.4 from trying to login to the "angel" account.

3vilh4x0r could still brute-force accounts, but it would become much more tedious. And innocent users of proxies don't get snagged.

The one snafu is if the real owner for "root" and 3vilh4x0r are on the same IP (which I guess could happen if you have an enemy at school or something), but that seems like a fairly minor problem to be concerned with.

bjaspan’s picture

I realize that my previous message was unclear.

When I said, "after X failed attempts in Y minutes, all login attempts will fail for Z minutes," I meant that as applying all to a single account. X failed attempts to login as user foo in Y minutes will disable all logins for user foo for Z minutes. User bar is unaffected.

I think doing anything with IP addresses will be useless or worse. If we get X failed attempts for user foo in rapid succession, do we really care which IP address it is coming from?

OTOH, imagine a world in which brute-force login attempts are as common as Windows vulnerability scans (i.e. constant for every host on the net). In this case, do we really want to disable logins at all? I'm sure this situation is coming. Maybe we just have to accept password guessing as a fact of life and ignore it.

spiffster’s picture

Why not implement a secondary question / answer after a password has been successfully guessed? Im sure this would discourage many of these douche bags. Also, it would help to lobby and support laws that execute offenders. :)

rorysolomon’s picture

What is the current status of this? Has a patch been installed or is there any plan for Drupal 6?

Thanks.

lilou’s picture

Version: 6.x-dev » 7.x-dev

Bump.

duntuk’s picture

as indicated in #13, (challenge-response) captcha at login would be a viable solution...

http://drupal.org/project/captcha

tormu’s picture

Having to enter an answer to captcha every time I login would be quite annoying.. If there would be a way to introduce the captcha after the first incorrect answer, it would be a lot better way. That way most of the legitimate users never have to enter it, right?

grendzy’s picture

Status: Active » Needs work
FileSize
792 bytes

Good! This is probably the best idea so far. After X attempts in Y minutes, logins require a captcha (or perhaps a proof-of-work). Because captcha is in contrib, I don't think this approach is likely to become part of core, however I could see it as an additional feature of one (or both) of these modules:

http://drupal.org/project/captcha
http://drupal.org/project/hashcash

The big win is this at worst, a denial-of-service attack will only inconvenience the legit user with a captcha. Of course the captcha's are a constant arms race... but it will certainly make an attacker work a lot harder.

One thing we will have to watch out for: Login attempts may not always come through the /user form. There is also blogapi, and perhaps other means as well. Often other modules will call user_authenticate directly, which will bypass hook_user for failed login attempts.

What does everyone think about a patch for user_authenticate() that invokes hook_user('authenticate', &$form_values, $account)
?

(I haven't tested this at all, just floating the idea. And of course the patch would have to include updated docs for hook_user with the extra $op, and probably a unit test).

grendzy’s picture

FileSize
600 bytes

Actually, that hook might be a little redundant, since we already have hook_user('login' ....). No point in making the hook implementor re-check the password to take action against bad logins.

Here's one that targets failed logins specifically.

Dave Reid’s picture

Why not just run a flood control when hook_user('login') is invoked n times for user x?

grendzy’s picture

@Dave Reid: As I understand the current hook_user('login'...), it's only invoked on successful logins. So it can't be used to act on failed logins. There is hook_user('form'...), whereby an additional ['#submit'] callback could be added.

However, an attacker could bypass a form-based hook by hammering on the blogapi XML-RPC interface.

Or is there another way to use the existing hook I'm missing?

One other thought on hashcash... I tested the module, and while novel and interesting, it actually seems pretty useless as a brute-force countermeasure. The hashcash module requires (on average) 100 MD5 hashes to be computed per login. This is such a tiny amount of work that it would not slow an attack at all (MD5Crack can compute > 40 million hashes per second on current PCs). :-(

Dave Reid’s picture

@grendzy, you're right. I guess right now I could go either way on an extra hook_user op, but I guess a better solution is to use hook_form_alter on the login form to add an extra validate function. The additional validation function implements a flood control, etc.

webchick’s picture

Issue tags: +Security improvements

Tagging this so we can start collecting issues like this.

sun’s picture

How do others solve this? Easy - after X attempts, send and require an e-mail confirmation to re-enable the login.

Dave Reid’s picture

Hot dog...issue tagging! I didn't even notice it yet today!

@sun, From what I've encountered, if the same user account has had multiple bad attemps, the account is blocked. The flood control contrib module I'm working on is going to provide triggers when each flood control threshold is met so actions like block IP/block user actions can be assigned to them.

sun’s picture

@Dave Reid: See above. btw: Did you lost your IRC nick credentials? ;)

greggles’s picture

See http://drupal.org/project/login_security for a current example implementation.

I'm not sure that a time-out is a good idea for core.

pwolanin’s picture

Priority: Normal » Critical

We already have a flood control API that is used by contact module, for example. By default, we lock you out after 3 contact attempts for an hour. Why is the contact form subjected to higher security than login?! http://api.drupal.org/api/function/contact_personal_page/7

wrt this research article, bumping to critical:
http://www.usenix.org/event/hotsec07/tech/full_papers/florencio/florenci...

Seems like we should use (and maybe improve) the existing flood API?

pwolanin’s picture

Status: Needs work » Closed (duplicate)

this issue has a much more recent patch #485974: Improved security: rate limit login attempts.