(ichimunki) Re: crypt
by ichimunki (Priest) on Nov 08, 2001 at 01:35 UTC
|
Without encrypting the HTTP session using SSL (i.e. HTTPS), there is no method of storing information in a cookie that is secure since the packets containing the cookie are sent in the clear over the net. I don't even need to decrypt the password to use such a cookie, if I can get my hands on the packets as they pass from the client to the server.
That said, I'd think that for most many non-commerce uses such a system is sufficient if there is a call to a cookie destructor at some point. Either a short expiration date on the cookie both on the client side and on the server side(so that a hijacked cookie has a short viability) or a "log me out" button, so that it is up to the user (in an apparent and easy way) to clear that cookie from use (and make it so they have to login the next time around-- of course, every login presents a possible target for interception as well). | [reply] |
Re: crypt
by kschwab (Vicar) on Nov 08, 2001 at 02:04 UTC
|
Aside from the packet sniffing attack, what you are
doing is very vulnerable to a dictionary attack.
You could mitigate this risk significantly by using
a random salt instead of the constant "01".
Basically, if anyone finds out that the salt is "01",
they only have to make one pass to encrypt every word in
a dictionary and guess passwords. Randomize the salt, and
they have to make a lot more passes.
One way to do that:
my @chars=(a..z,A..Z,0..9,'.','/');
my $salt= $chars[rand(@chars)] . $chars[rand(@chars)];
Update:
Another option would be to use Crypt::PasswdMD5, which has the same basic interface as good old crypt, but it supposed to be more
resistant to dictionary attacks. (YMMV, I'm not a cryptographer)
| [reply] [d/l] |
|
|
With the understanding that no non-core modules can be used we probably cannot get by with anything but crypt(), so the suggestion to add a random salt is a good one (although don't forget to store it in the database!).
If going with a non-core module, I'd rather see that the password itself isn't even put in the cookie, maybe the MD5 or SHA1 hash, but even then to what end? We may as well use a semi-random or other mostly unique key in our cookie and prevent an attacker from having any clue how the passwords are stored in the database. That way we limit cracks to sniffing and to bad password selections on the part of users.
And to prevent cracking, I'd suggest a limit to the number of tries a user gets-- although this brings up a DOS problem, which under the circumstances is likely to be a less invasive "crack" than unauthorized access to the system.
{Just my additional two cents on this}
| [reply] |
|
|
ichimunki++
That is the right idea. Rather than constantly passing the password back and forth, generate a random session ID when the user has successfully logged in. I suggest your session manager also tie a session to the IP it originally was started from, and that sessions be expired pretty quickly after some inactivity (when the user hasn't sent a request for, say, 30 minutes - though your specific application may make longer idle delays necessary).
| [reply] |
|
|
Or if you don't like extra (read: temporary) vars, the canonical:
my $crypt = crypt( $password, join( '', ('.', '/', 0..9, 'A'..'Z', 'a'
+..'z')[rand 64, rand 64] ) );
-- Snazzy tagline here
| [reply] [d/l] |
|
|
I'd use
$enc_password = crypt($plain_password, $plain_password);
But this method has the problem that abcdefghi will result the same as abcdefghij or any password beginning with the same 8 characters.
You could use the first and the last character as salt to avoid this problem (the salt is cutted to two characters anyway).
colli
| [reply] [d/l] [select] |
|
|
I'm not clear on why you would do this.
Once the "bad guy" figures out what you've
done:
open(DICT,"/usr/dict/words");
while(<DICT>) {
chomp;
my $guess=crypt($_,$_);
# insert some LWP code here to attack
# the web page
if ($it_worked) {
print "$user: crypted password is $guess\n";
}
}
close DICT;
On the other hand, if you randomize the salt, the loop
above becomes an inner loop. Then the "bad guy" has
to add an outer loop that runs up to 64*64 times. | [reply] [d/l] |
Re: Is this use of crypt() appropriate?
by Nomis52 (Friar) on Nov 08, 2001 at 18:16 UTC
|
I'm doing a similar thing but using a session id.
On sucessful login a session id is created using the following:
User name
HTTP User Agent
IP address <- can change paticularly with aol users and proxy servers
Day-of-the-year
and a "secret" constant string
This is fed to MD5 which computes the checksum of it and stores it in a cookie along with the users name.
Everytime a script is requested the session id is checked by re-creating the session id and comparing it to the one in the cookie.
For someone to fake a session id they need all of the above information including the "secret" string and what order i joined them together.
The logout is simple, just delete the session id from the cookie.
More secure IMOHO than sending any form of the password over the net to store in a cookie. (Remembering it was sent once when the user logged on but for that you should use ssl).
I found this site very usuful when putting this togeather.
Good luck
Nomis52 | [reply] |
|
|
Thanks Nomis, that is very helpfull.. would you be able to post some example code of the session in action? It would be very appriciated, many thanks
| [reply] |
|
|
Ok this is how I did it. Note I'm very new to perl programming so this probably isn't the best way.
Assuming you have authenticated the user (from a database or text file or where-ever), and $user is the user's id
use MD5 ;
my $md5 = new MD5 ;
$md5->reset ;
my $yday = (localtime)[7];
# create certificate / session id
my $certif = $user . $yday . "do4k.g0" . $ENV{'HTTP_USER_AGENT'} .
+$ENV{'REMOTE_ADDR'} ;
# encrypt certificate
$md5->add($certif);
my $enc_cert = $md5->hexdigest() ;
# set cookie
print "Set-Cookie: SESSION=$enc_cert; path=/\n" ;
print "Set-Cookie: NAME=$user; path=/\n" ;
# and continue
print "Content-type: text/html\n\n" ;
print "Your logged In!" ;
Then everytime the script is called get the certificate out the cookie and recreate a certificate and compare the two.
# $session and $user came from cookie
use MD5 ;
my $md5 = new MD5 ;
$md5->reset ;
#create ceritficate
my $yday = (localtime)[7];
my $certif = $username . $yday . do4k.g0 . $ENV{'HTTP_USER_AGENT'} .
+ $ENV{'REMOTE_ADDR'} ;
# encrypt Certificate
$md5->add($certif);
my $enc_cert = $md5->hexdigest() ;
#compare
if($enc_cert eq $session) {
# we're logged in - run script ;
} else {
# we're not logged in - disp error msg
}
And a logout can simply be done with a
print<<"END" ;
Set-Cookie: SESSION=; path=\
Set-Cookie: NAME=; path=\
Content-type: text/html
Your logged out now
END
It would probably be wise to set expiration times for the cookies. Using the $yday means each certificate will expire at midnight which could be a problem.
Anyway I hope this helps
Nomis52 | [reply] [d/l] [select] |
Re: Is this use of crypt() appropriate?
by Anonymous Monk on Nov 08, 2001 at 15:35 UTC
|
Another post by me, I keep re-reading the posts above and thinking of new ways to approach this. I'll explain how I am doing my login management at the moment: When someone signs up their password is encrypted in a MySQL database, when they login their password is encrypted again and stored in a cookie, each request checks that the password in the cookie matches that in the database.
The theory above using the below code is the one that appeals to me the most
my @chars=(a..z,A..Z,0..9,'.','/');
my $salt= $chars[rand(@chars)] . $chars[rand(@chars)];
The only problem with this is that it will encrypt the password randomly, so I would need to decrypt it in other areas of the script and store the mysql password in plain text, rather than also encrypted.
My understanding of crypt() is fairly limited and I have learnt a fair ammount from the posts above, I would greatly appreciate it if anyone could provide some advice based on what I have posted here.. Thanks very much | [reply] [d/l] |
Re: Is this use of crypt() appropriate?
by Anonymous Monk on Nov 08, 2001 at 14:20 UTC
|
Thanks all, the idea of using sessions does sound more appropriate, however, I am unsure how to approach this. Any suggestion on how i would go about ahieving this? | [reply] |
|
|
Sorry to post twice, re-reading all the above posts, the use of a random salt does sound easier to implement..how would I use a 'salt' when encryptin a password ? simply by replacing '01' with '$salt' ? Thanks
| [reply] |
|
|
Creating a session ID is simple, and probably better than worrying about how to encrypt a password and decrypt it reliably. If you have the MD5 module available, why not just hash the password against the time (or using crypt you could salt the password or the userid with the last two digits of time() or a random key and send this back in the cookie-- you then store this session ID in a field in the DB and do a simple compare each time. For storing the session ID you could go the extra step of using the MySQL crypt() function to keep people with DB access from easily reading it there.
As I said before, if I can intercept the transmission of the cookie I can spoof all day long unless you also include an IP check (and I might be able to spoof IP too, just not as easily) or auto-expire.
As was pointed out, the most vulnerable area is going to the dictionary crack, since it relies on the strength of the user passwords themselves (and users are notorious for choosing poor passwords). All that is required to execute a dictionary crack is a script that emulates the input from your login form and runs through the dictionary trying sensible combinations of words, etc etc. So I wouldn't worry too much about what's in your cookies except that it not easily reveal the password itself and that it not be easily predicted.
| [reply] |
|
|
There is an alternative to random session keys which also avoids cookies (for those who don't like cookies):
Have the user log in once with username and password
and generate a long random string to use as the
session key. Record the user, ip, current time, and
session key in a database of active sessions.
All URL requests for web pages, scripts, must be preceeded
by the session key:
http://yoursite.com/get.cgi?bdjaiwmcvndjqidm+the_page.html
Everytime a page is requested, you check the session key, ip, and the current time against those in the database of active sessions. If the match fails, or the current time is longer than the lifespan you have chosen for the key, you request that the user log in.
Using this approach, the password is only transmitted once, at the start of each session, and the session key is of limited use if it is intercepted because it has a short lifespan and must match with the ip.
Note: The user never has to type in the key themselves, because you serve them up pages with the key already included in the links. This is not a major security
problem for the reasons stated above.
Joe.
| [reply] |
Re: Is this use of crypt() appropriate?
by ehdonhon (Curate) on Nov 08, 2001 at 22:25 UTC
|
It looks like other people have already suggested
alternatives to crypt, but if you are set on using crypt,
here's two suggestions
1. You should always send the user's entire encrypted
password as the salt, not just the first two characters:
$password = crypt($INPUT{'password'},$encrypted_password);
2. If you are storing the passwords in mysql, there might
be an easier, non-perl solution to authentication for you
using the mysql builtin function 'PASSWORD':
my $user = $dbh->quote( $INPUT{'user'} );
my $input_pass = $dbh->quote( $INPUT{'password'} );
my $sth = $dbh->prepare (
"SELECT pass as encrypted_password,
PASSWORD( $input_pass , pass ) as input_password
WHERE user like $user
FROM passwd_table " );
| [reply] [d/l] [select] |
Re: Is this use of crypt() appropriate?
by BMaximus (Chaplain) on Nov 09, 2001 at 01:35 UTC
|
ichimunki++ from me too. However you can secure a cookie without SSL. See my module that I use for mod_perl to encrypt the contents of a cookie, Apache::Cookie::Encrypted. Very easily done with Crypt::Blowfish and it even has a pure Perl implementation.
I'm most cases. If a user doesn't have the module and has SSH access to their account they usualy can install the module into their workspace. If they don't have access, there usualy is a way to finagle it in if its a pure Perl module. It doesn't hurt to ask the Admin if they would install any modules needed. If they don't want to install it and they cite a good reason for it, fine. If the reason is bad it usualy is a sign of a lazy admin, you're better off taking your business elsewhere.
The others have more than explained the proper use of Crypt.
BMaximus | [reply] |
|
|
Cool module. But I still think it leaves the cookie vulnerable to sniffing, which is all that is needed. If I can replicate your cookie, encrypted or not, I can pass it to the server as if I were you and more likely than not the server will believe everything is fine. That's the reason we have to encrypt the transmission itself and not merely the contents of the cookie. That way an attacker has almost no chance to guess which parts of the transmission are the cookie and re-use them.
| [reply] |
|
|
Good point. But I doubt that a person who is sniffing on the net would get the whole thing. It would take a person being on the same LAN to get the whole cookie with a sniffer. As I was thinking that a way to combat this would be to add the IP address of the computer the cookie is being sent to into the encrypted contents. However something like that would cause a problem with anyone who is using a proxy (like AOL). If I were doing E-Commerce I would most definatly use SSL. Any way of securing a cookie without SSL? Taking an MD5 of the cookie won't do it since the cookie is not changed. Where does being carefull cross over to being overly paranoid?
BMaximus
| [reply] |
Re: Is this use of crypt() appropriate?
by DrManhattan (Chaplain) on Nov 21, 2001 at 18:10 UTC
|
I issue session keys to my users with a symmetrical encryption algorithm rather than a one-way hash. I concatenate the user's username, ip address, a timestamp, a lifetime, and a random number together, then encrypt them with Crypt::CBC and send them out in the form of a cookie. The server validates the cookie by decrypting it, verifying the connecting client's ip address, and ensuring that the timestamp + lifetime is greater than the current time.
Storing the ip address + timestamp/lifetime in the cookie mitigates the risk of replay attacks (i.e. someone else attempting to access the site with an identical cookie). Storing the random number makes a known plaintext attack on the encryption key much more difficult.
-Matt
| [reply] |
Other methods
by Stevie_G (Initiate) on Nov 08, 2001 at 22:23 UTC
|
From reading what you're looking at you may want to consider using either a mod-perl enabled server for authorization (possibly using non-mod_perl servers for other tasks that only require a valid user). If mod_perl is an option, I would suggest Apache::AuthCookieDBI.
Failing to have a mod_perl server, I would recommend that you use a similar system to the Apache::AuthCookieDBI module. Have a string of username, ip address, expiration time (this needs to be short in order to avoid replay attacks), and possibly a session id. MD5 these with a 'secret key' that is constant across all sites and you've got a better system. If you want to be even trickier, have the expiration time in the session and in the cookie, and check to see they're the same.
Don't put the password anywhere near the browser if possible! See Apache::Session for a non-apache based session system. | [reply] |