http://www.perlmonks.org?node_id=289569

belize has asked for the wisdom of the Perl Monks concerning the following question:

Which would be the more efficient way to limit the number of login tries to a password protected website:

1. Using cookies (can't the hacker just erase cookies on his machine to get around tis)
2. Generate a text file with the hackers $ENV{'HTTP_REFERER'} and count the number of tries

Is there a better way or does anyone know of a snippet to take a look at?

Replies are listed 'Best First'.
•Re: Password hacker killer
by merlyn (Sage) on Sep 07, 2003 at 14:00 UTC
    Because HTTP is stateless, there's no definite way to know that two hits are coming from exactly the same person (or even the same browser).

    You could watch for many failed attempts from the same IP address, but that will get false positives on proxies, and false negatives from AOL or dialup customers. Definitely don't bother with cookies or referer: any bad guy worth their salt is going to strip those. In fact, it's trivial to construct a LWP-based bot that blows both of those off.

    -- Randal L. Schwartz, Perl hacker
    Be sure to read my standard disclaimer if this is a reply.

      Because HTTP is stateless, there's no definite way to know that two hits are coming from exactly the same person (or even the same browser).

      As you seem to know quite well, there are standard ways to add session functionalities to http. The client can of course perform a clean request at each time, by cleaning cookies and filtering out session parameters, but the same is true of stateful protocols. It is equally difficult to stop an attacker from performing repeated ftp/ssh/telnet login attempts., so I'd say that the statelessness of http is not the issue here.

      Cheers

      Antonio

      The stupider the astronaut, the easier it is to win the trip to Vega - A. Tucket

      HTTP is a stateless protocol, but it allows state via cookies and CGI parameters if both sides cooperate. The server already wants to cooperate, so you just have to provide an incentive to the client.

      One way to do this would be to have a cookie that a person using the interface normally would receive, which tracks how many times they've tried to log in and makes them wait progressively longer or whatever else you want to do. The cookie would have to be secured in some way that would make it impossible for the client to just make one up, or to re-use an existing one. You could randomly generate cookie IDs and store them in a database, deleting them when they're used, or use cryptography to make this work (though no crytographic scheme comes immediately to mind).

      Once you have a way of generating secure cookies, you can simply check if their cookie is valid, and if they don't present a valid cookie, you penalize them in a way that makes it very difficult to brute-force a password. sleep(30) would be a good penalty.

      Something like this is what I'm thinking of (this is perl-like pseudocode):

      if (!$login) { &print_login_page; exit(0); } my($numtries)=check_cookie($cookie); if ($numtries) { $waittime = 5*($numtries-1); } else { $waittime = 30; } sleep($waittime); if (check_pass($login,$password)) { welcome(); exit(0); } else { set_cookie(numtries => $numtries+1); bad_password(); exit(0); }
      You would enfoce the cookie's security in check_cookie and set_cookie.
        For several reasons, this is not a good solution to the problem:

        First off, you penalize any valid users that want to log in for the first time.

        Secondly, any attacker can just start up a bunch of requests at the same time (let's say 10 requests) and still get way more attempts per second. Try to stop that and you'll create a situation where your security system will probably become more convoluted and difficult to test (thus probably still not working correctly).

        Anyways I'd go for matsmats++ solution, or go for full client SSL certificates if you can affort the trouble and money.

        -- #!/usr/bin/perl -w use strict;$;= ";Jtunsitr pa;ngo;t1h\$e;r. )p.e(r;ls ;h;a;c.k^e;rs ";$_=$;;do{$..=chop}while(chop);$_=$;;eval$.;
Re: Password hacker killer
by matsmats (Monk) on Sep 07, 2003 at 14:27 UTC

    If your password is connected to a username, and said data is registered in a database - count the login attempts there. My favourite implementation of this is to double the response time from the server for every failed login-attempt on a username, slowing a brute force password guessing attack to a halt, but not necessarily bothering a regular user with throwing him out or something annoying like that.

    Basically, as merlyn points out, you can't trust what is sent to you, so you have to connect the count of login tries to something you know is true. A username connected to the password would be most natural, I think.

    Depending on the scale of what you're doing this for, an IP-adress check could be enough. The false negatives from AOL/dialups are not likely, I think (depending on the strength of your passwords) - and false positives from proxies could be taken care of by raising the number of allowed attempt to cover what goes as a expected count from said proxies. Not perfect, though.

Re: Password hacker killer
by abell (Chaplain) on Sep 07, 2003 at 14:23 UTC

    You can set a limit on the failed attempts coming from the same IP for a given user name. Say that after three failed attempts in the same 10 minutes, login attempts for the given username from the same IP are rejected for one hour. This way a legitimate user would only be hurt if he made a mistake on his own password several times in a row, and an attacker wouldn't be able to DOS a user unless he were also able to spoof the user's IP address.

    Cheers

    Antonio

    The stupider the astronaut, the easier it is to win the trip to Vega - A. Tucket
      It's for this task that I wrote Tie::Scalar::Decay, which implements scalars whose values change over time. For each failed attempt, I would increment a scalar. Every N amount of time the value would decrease by F. But if the value went above a particular value, I would assume an attack was underway and take action. Those options can be tweaked so that a real user who's forgotten his password won't be locked out, but an automated password-guessing bot will be locked out.

      Depending on your implementation, you may be able to use this module, but if you can't, then the basic idea of it is simple and should be easy to implement some other way. If you do have to reimplement it, I'd be glad to help or to accept suggestions or patches.

Re: Password hacker killer
by Zaxo (Archbishop) on Sep 07, 2003 at 14:43 UTC

    It's not suitable for all applications, but you can send failed logins to some innocuous page. Apache's ErrorDocument setting in .htaccess can be used for that. You can point that to a perl script which logs whatever you want to see and produces a joke, a stunt, or just something harmless which must be parsed to discover that a guess has failed.

    That way you don't need a doomed effort to maintain state, as merlyn explained, in a stateless protocol. All you've done is make guesses more time-consuming and difficult. That's how passwords are supposed to work.

    After Compline,
    Zaxo

      If you make it difficult to discover a guess has failed, but easy to discover it has succeeded, you haven't really gained anything. You'd have to make it hard to tell whether you were logged in succesfully, and I can't help but think that's bad user-interface design...
Re: Password hacker killer
by liz (Monsignor) on Sep 07, 2003 at 14:31 UTC
    I would definitely count the number of unsuccessful tries for a particular user. Then put in a sleep of one second for each unsuccessful try. So the first unsuccessful trye wil take 1 second, the second 2 etc. etc. This will at least slow the attack down, but may have turned it into a unwanted DoS attack if more attempts are made before the previous has returned its result..

    So you would need to be a little smarter, by somehow flagging that a sleep after an unsuccessful attempt is occurring. And simply break the connection on any attempts being made while in a "sleep" period (as this indicates a parallel, and most likely programmed attack). If you find two or more parallel requests, I think you can safely assume you have an attack on your hands and appropriate actions (notifying admins, blocking IP number, etc) may be needed.

    Of course, once the user properly supplies the password, reset the failed tries counter.

    No code, just a principle course of action. Hope it helps.

    Liz

Re: Password hacker killer
by davido (Cardinal) on Sep 07, 2003 at 20:06 UTC
    How about this? (Just a thought that came to me. There may be problems with it, but then again, maybe it has some merit):

    After three failed login attempts, send the user an email at his registered email address:

    John Doe: You have, or someone pretending to be you has attempted to log into xxx.com unsuccessfully 3 or more times. To provide you with the utmost in security, xxx.com has put your account on a temporary hold. You may remove this hold by logging in as follows. The next time you log in (and that time only), use your existing user name, plus the text, "+ZRYU3" (without the quotes), appended to your username. Your password should be entered in the usual fashion. If the unsuccessful login was the result of forgetting your password, click THIS LINK to have your registered password hint emailed to you at this email address. We are sorry for the inconvenience. If you have any questions or need further assistance you may email support at login-support@xxx.com. Sincerely, .....

    I think the preceeding text pretty much explains the pholosophy. If three attempts fail, suspend until the user logs in with 'username+REY3Q', the suffix being a random set of ASCII characters known to be available on just about any keyboard; perhaps \w or \w\d.

    Implementation wouldn't be terribly complex, and the only difficulty would be if users don't keep their email address up to date, or if they're too new to technology to understand the instructions.

    If you're still concerned with a bot knowing about the suspension and trying to thwart it by guessing at the username alteration as well as the password, implement one of the $delay*=2; solutions for every guess at username. The delay still enables DOS attacks, but the attacker has to go an extra layer into the onion to accomplish the attack. And also, by adding an extra six unknown digits to the username, in addition to the already unknown password, you've made unauthorized access difficult enough that the attacker is likely to seek more fertile ground.

    UPDATE: BrentDax suggested emailing a "...click on THIS LINK..." to the real user's email address. I think that's a fantastic modification to my original proposal, but believe that for those whos email clients don't support clickable links, and those whos email clients break links by mangling them in the process of wrapping text, it doesn't hurt to provide the "log in next time only username+random_stuff" as an alternate. There are people who simply can't click on a link in email and expect it to work right. The approach of enabling either option seems to be a good solution for those people.

    Dave

    "If I had my life to do over again, I'd be a plumber." -- Albert Einstein

      If you're going to do something like that, the key paragraph might as well be:
      To remove the suspension, click on THIS LINK. You will then be able to log in normally. If you have forgotten your password, the link above will offer to e-mail your password hint to you, or send you a new computer-generated password.
      The "THIS LINK" would contain a randomly-generated code of some sort. This keeps people from having to deal with weird "add this to your username" type things.

      =cut
      --Brent Dax
      There is no sig.

Re: Password hacker killer
by calin (Deacon) on Sep 07, 2003 at 17:10 UTC
    You can challenge the user with a so called Reverse Turing Test. It's basically a low quality and partially scrambled rendering of a random text or number (to prevent OCRing) that the user must interpret and submit back before being allowed to continue with the log-in procedure. See this paper for more info.

        What about challenging them with simple pseudo riddles. Although not perfect it could work. With enough variation in the questions and the format you could make it difficult.

        Please enter the answer to the following question: (number of days in a year) + (the hours in a day) + (the number of wis +e men)

        People friendly, computer not. At least it would stimulate growth in NLP and common sense bots :)

        ___________
        Eric Hodges
        WOW! Thanks for the heads up, that could have bitten me in the butt hard... I had considered that for a system I am working on, guess that one goes off the drawing board now :) Just goes to show, no matter how much you think out a solution, there is always something lurking around the corner that you just don't expect.
Re: Password hacker killer
by allolex (Curate) on Sep 07, 2003 at 14:24 UTC

    Use user-level authentication and limit the number of retries per user to something like four and then put a wait period between unsuccessful login series. So if someone makes four unsuccessful login attempts in a row, block the user account so that that user cannot log in for the next hour, no matter the password, or until you reset the account.

    Also make sure your passwords are good in the first place. No dictionary words, no names (there are dictionaries for those, too). You could maybe use something like Data::Password, although I cannot personally vouch for it.

    Good luck keeping them out.

    --
    Allolex

      This method leads the way for an effective DOS - if I want to prevent you from logging in, I just write a script that repeatedly tries to log in as you, with a wrong password. You won't be able to ever get at your account again.

      You need to block at least only a certain IP address, then only AOL users can block AOL users ...

      perl -MHTTP::Daemon -MHTTP::Response -MLWP::Simple -e ' ; # The $d = new HTTP::Daemon and fork and getprint $d->url and exit;#spider ($c = $d->accept())->get_request(); $c->send_response( new #in the HTTP::Response(200,$_,$_,qq(Just another Perl hacker\n))); ' # web

        Yes, thanks for pointing that out. It would therefore be logical to block the IP instead of the user for the same time period, or better yet block the combination of user/IP.

        --
        Allolex

Re: Password hacker killer
by waswas-fng (Curate) on Sep 08, 2003 at 18:45 UTC
    Some of the more complicated password/hacking blocking tools will do the following.. Keep a running database of:

  • IP -> Username good auth
  • IP -> Username bad auth

  • Username -> Current session list

  • With these three time stamped pieces of information you can do the following:

  • Test for repeated usernames with bad auths from one IP, and tag the IP as a cracker for X minutes (you have to have a rolling target for this so you can set the weight limit high enough to get few false blocks -- meaning 5 bad user/pass combos with unique users in 30 seconds triggers a block while 5 bad auths over 5 minutes does not.)
  • Many same user bad auths with the same IP (or different as most password cracking software will use huge proxy lists to hide the source of the attack) can trigger a administrative lockout of the account -- You should give the user the ability to easily unlockout their account once every X minutes via another form or via customer support.
  • Test for multiple good auths from different IP addresses with unique session ids. This should take into account that the end user may have a few different computers live on the site at once -- but there is no reason to see 15 active sessions from 15 different IP addresses.

    Just make these test flexible enough to allow for normal allowed use of the site while catching blatant cracking attempts.


    -Waswas



    EDITED: Spelling mistakes fixed gogo dyslexia.

      Note that the last one may hurt AOL users -- all AOL http requests come from one of several HTTP proxies at (psudo-)random.


      Warning: Unless otherwise stated, code is untested. Do not use without understanding. Code is posted in the hopes it is useful, but without warranty. All copyrights are relinquished into the public domain unless otherwise stated. I am not an angel. I am capable of error, and err on a fairly regular basis. If I made a mistake, please let me know (such as by replying to this node).

        Its also worth noting that WebTV users move between IPs for every page hit so you'd see the same session showing up on multiple IP addresses.

        Test for multiple good auths from different IP addresses with unique session ids.

        I think you missread this, this means unique username <waswas-fng> from random IP addresses each with their own session going.
        <br
        User:IP:Session waswas-fng:10.128.172.10:000001 waswas-fng:10.128.172.10:000002 waswas-fng:10.128.172.10:000003 waswas-fng:10.128.172.10:000004 waswas-fng:10.128.172.10:000005 waswas-fng:10.128.172.12:000005 waswas-fng:192.168.66.11:000004 Would be "OK" Where: User:IP:Session waswas-fng:12.128.172.10:000001 waswas-fng:231.128.172.1:000002 waswas-fng:12.128.172.10:000003 waswas-fng:231.128.172.1:000004 waswas-fng:192.168.52.1:000005
        Would show many users using the same account with unique sessions on different networks (shared password or hacked access).

        -Waswas
Re: Password hacker killer
by GermanHerman (Sexton) on Sep 09, 2003 at 00:55 UTC
    From my experience building and block web robots and script kiddies the following have been the most effective:

    An extremly complex cookie mechanism in a dynaic external javascript file

    Cookies sent in with random images on the page

    IP address tracking

    Tracking what order your parameters come in on which broswers

    Wheither or not they sent it as a post or a get.

    Whether or not requests are coming in at regular intervals (if they are coming in at intervals less then 5 seconds then it is probably a robot of some kind.)

    If the client has requested to logon under a vastly different user name.

    If you are trying to keep people web web robots rfom downloading your entire site you can give people bandwidth quotas (I think apache does this though I'm not sure)

    Instead of sending the "he is logged on this is his id" cookie with the logged in page send it from a style sheet on that page.


    All of these methods can be worked around (the ip tracking one being the hardest) but implementing a set of them could make someone thing twice about how hard they want your content.

    -Douglas