http://qs321.pair.com?node_id=101247

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

Looking for feedback on the security model that I have for a particular site. We've been asked to encrypt only the login process (SSL). This site is for an administration console and will only have a few users. They are required to have cookies enabled. Here's basically how it works:

The user enters a username/password combination. This is sent over an ssl connection to the authentication script. The password is hashed using Digest::MD5 and a salt that is read from a non-web accessible file. This is compared to the hashed password in the database. If they don't match, they are redirected to the login screen up to seven times, after which they are locked out until an administrator unlocks them. If successful, a digest is created with the following algorithm:

sub _create_session_digest { # Please note that we will compare the digest against what's in th +e database rather than # recompute. It's quite possible for someone's ip address to chan +ge with every request. my $self = shift; my $md5 = new Digest::MD5; my $remote = $ENV{ REMOTE_ADDR } . $ENV{ REMOTE_PORT } . $self->{ +_salt }; my $id = $md5->md5_base64( time, $$, $remote ); $id =~ tr|+/=|-_.|; # Make non-word characters URL-friendly $id; }

This digest is returned as a cookie. Subsequent accesses to the admin console will return the digest and compare it to the database. If this takes too long, their session times out (controlled by the script, not the cookie) and they must relogin. If the digest matches, a new digest is created, stored in the database, and sent back in a cookie. Except for the initial SSL connection when the username and password are submitted, they will never again be sent. Here's the main code that controls this:

# Everything in ALL CAPS is a constant sub validate_and_get_new_cookie { my ( $self, $cgi, $user, $pass ) = @_; my $cookie = $cgi->cookie( SESSION_COOKIE_NAME ); # delete sessions older than that session ID's allowed timeout $self->_clear_old_session( $cookie ); if ( defined $user and defined $pass ) { # they're submitting a username and password, so let's try to +log them in my $attempts = $self->_count_login_attempts( $user ); $self->_lockout if $attempts >= MAX_LOCKOUT_ATTEMPTS; my $db_pass = $self->_get_password( $user ); my $user_pass = $self->_create_digest_from_password( $pass ); if ( $db_pass eq $user_pass ) { return $self->_create_digest_cookie( $user ); } else { my $attempts = $self->_update_attempts( $user ); $self->_log_bad_attempt( $user, $pass ); $self->_lockout if $attempts >= MAX_LOCKOUT_ATTEMPTS; print $q->redirect( LOGIN_PAGE ); } } else { # no user or password, so we'll try to validate with the cooki +e my ( $user, $active ) = $self->_get_digest_info( $cookie ); if ( ! defined $user or ! $active ) { # didn't get a user name or they've been inactive too long print $q->redirect( LOGIN_PAGE ); } else { return $self->_create_digest_cookie( $user ); } } }

In the future, I plan to add a 'bogusLogin' table to the database. The intent is to even lockout non-existent user IDs after MAX_LOCKOUT_ATTEMPTS so that crackers can't use the lockout feature to determine if they have a valid user id. Have I overlooked anything?

Cheers,
Ovid

Vote for paco!

Join the Perlmonks Setiathome Group or just click on the the link and check out our stats.

Replies are listed 'Best First'.
Re: CGI Security Advice Sought
by Zaxo (Archbishop) on Jul 31, 2001 at 22:47 UTC

    I'm leery of using $ENV{'REMOTE_ADDR'}.$ENV{'REMOTE_PORT'} in the session id cookie. How will that interact with several connections through one nat box? The seven try lockout is probably good enough to alert you to a salt guessing effort, but the content of the cookie is spoofable, guessable, and tainted.

    With SSH a given, why not use the server's built-in authentication and session tracking?

    After Compline,
    Zaxo

      $ENV{'REMOTE_ADDR'}.$ENV{'REMOTE_PORT'} are not actually being used in the cookie itself. They, along with the salt and the process id ($$) are merely being used with Digest::MD5 to increase the likelyhood of generating unique session ids. In retrospect, I suppose that I should also throw a randonly generated number in there.

      We are not using the server's built in authentication and session tracking because we hope to reuse this code on different sites and cannot guarantee which server we'll be using. This seemed like a more portable approach.

      As for the contents of the cookie being spoofable, guessable, and tainted:

      • Spoofable:

        If the digest in the cookie doesn't match what's in the database, they simply get redirected to the login.

      • Guessable:

        To guess how to generate the digest, they'd have to figure out the salt, which I think is non-trivial. If they sniff it, they could possibly hijack a session, but that's why the digest is changed on every access. They attacker would have to sniff the cookie and submit it before the user clicked on another link (this is the big weakness of not having everything over an SSL connection). If they do sniff the cookie and don't send it soon enough, either a new digest will be in the database or the database-controlled session timeout will block them.

      • Tainted:

        Shouldn't matter. At no point is anything done with the cookie data except check to see if it is the same as what's in the database. Oh, there is one exception: it's included in an SQL statement for clearing old sessions, but even then a placeholder is used in the SQL to ensure that it's properly quoted.

      Cheers,
      Ovid

      Vote for paco!

      Join the Perlmonks Setiathome Group or just click on the the link and check out our stats.

Re: CGI Security Advice Sought
by Cirollo (Friar) on Jul 31, 2001 at 22:07 UTC
    I use a system that is almost identical to yours. So far it has worked great, and I haven't been able to identify any major flaws or weaknesses in it.
Re: CGI Security Advice Sought
by Anonymous Monk on Aug 01, 2001 at 13:48 UTC
    I would suggest logging the ip, session id (serverside), along side the hashed version, and check those as well, when checking the cookie.

    Both of these can be snooped, but at least you can detect when someone is trying to circumvent your security, by spoofing the cookie.

    It might be even if you are changing the cookie hash 'often'.