Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations SkipVought on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Printing out username and password from .htaccess 1

Status
Not open for further replies.

youradds

Programmer
Jun 27, 2001
817
0
0
GB
Hi. I have just worked outthat $ENV{'REMOTE_USER'} holds the name of the login username that they submitted to access a password protected area. Is there another one that holds the password?

Thanks

Andy
 
Also, I have a script at (the form at the top for login), and it consists of;

Code:
#!/usr/bin/perl
# /\ Configure the above if needed.  ##
######################################
# print "Content-type: text/html \n\n";

use CGI::Carp qw(fatalsToBrowser);
use CGI qw(:standard);

$query = new CGI;

$forwhat = $query->param('forwhat');
$username = $query->param('username');
$password = $query->param('password');

if ($username eq "") { print "Location: [URL unfurl="true"]http://www.vgacd.com/[/URL] \n\n"; }
if ($password eq "") { print "Location: [URL unfurl="true"]http://www.vgacd.com/[/URL] \n\n"; }

if ($forwhat eq "prem") { print "Location: [URL unfurl="true"]http://$username\:$password\@vgacd.com/members/[/URL] \n\n"; }
if ($forwhat eq "free") { print "Location: [URL unfurl="true"]http://$username\:$password\@vgacd.com/free/[/URL] \n\n"; }
if ($forwhat eq "email") { print "Location: [URL unfurl="true"]http://vgacd.mail.everyone.net/email/scripts/loginuser.pl?loginName=$username&user_pwd=$password&login=Login[/URL] \n\n"; }

The annoying thing is that if I type;

username:password@vgacd.com/free in my browser, it lets me in. But although it is going to the correct URL it still won't log in and store the username / password.

Any ideas? :d

Thanks

Andy
 
Ack! Security violation!

Okay, so... first question: Is there an environment variable that stores the user's password? Absolutely not. Password's shouldn't be casually tossed around between the client and server. And even if they're passed only once, they should be so encrypted that a sniffer wouldn't even see that they were passwords (meaning that not only the password data, but the input field name and every other form element should follow that encryption ... viz. SSL). A password environment variable would not allow this.

Okay, so with that in mind, *never* *never* put a password in the visible query string. When you do that, you've opened up the user's password to anyone standing behind him/her, anyone that has access to the web logs on the server, and anyone that's sniffing the network. All the lines that look like:
Code:
if ($forwhat eq "prem") { print "Location: [URL unfurl="true"]http://$username\:$password\@vgacd.com/members/[/URL] \n\n"; }
or
Code:
if ($forwhat eq "email") { print "Location: [URL unfurl="true"]http://vgacd.mail.everyone.net/email/scripts/loginuser.pl?loginName=$username&user_pwd=$password&login=Login[/URL] \n\n"; }
Delete them now. Instead, you might consider using a token scheme where you provide the user with a unique cookie (unrelated to the pass), add them into a database and then on each page load, use a validation script which reads the cookie, validates it against the database, then allows access. Be sure to use session cookies which are stored in memory rather than the hard drive, and also have a timeout field in the database so a person who uses the computer after the authenticated user cannot use the previously validated account in the event that the previous user did not logout properly. Also maintain an IP field in the database with the HTTP_REFERRER address to avoid cookie forging. This is similar to MIT's Kerberos Authentication, which is relatively trustworthy.

And on that note, if you're going to be using scripts to assist in authentication, drop the basic http-authentication entirely, and use solely script based authentication. You will have much finer control over your user's sessions. The only time to use http-auth is when you have static files that you want to offer select access to.

I apologize if I sound harsh, but I see this practice far too much and I cringe at the thought that unsuspecting users are throwing their private data to the public. There are numerous sites on the Internet that offer articles/tutorials on good security habits for scripting. Read them all.

brendanc@icehouse.net
 
Actually, if you're using basic authentication (which .htaccess usually is), you CAN get the user's password. That one of the reasons that basic authentication is not considered a particularly good security method. The relevant information is stored in a somewhat encoded form in the HTTP_AUTHORIZATION environment variable. This subroutine will return a two element list consisting of the userid and password:
Code:
sub GetLogon {

my($authtype,$authstring) = split(' ', $ENV{'HTTP_AUTHORIZATION'});

return "unknown" unless $authtype =~ /basic/i;

# We COULD use the following two lines, but it would include
# code that we don't really need much.

#use MIME::Base64 ();
#$decoded = MIME::Base64::decode($encoded);

# The following code comes from perlfaq9

# remove non-base64 chars
$authstring =~ tr[A-Za-z0-9+/][]cd;
# convert to uuencoded format
$authstring =~ tr[A-Za-z0-9+/][ -_];
# compute length byte
$len = pack("c", 32 + 0.75*length($authstring));
# uudecode
my $authstring = unpack("u", $len . $authstring);
# Now we have userid:password. Split them up
my ($user,$pswd) = split(':', $authstring);
# return them
return ($user,$pswd);
} # GetLogon
Tracy Dryden
tracy@bydisn.com

Meddle not in the affairs of dragons,
For you are crunchy, and good with mustard.
 
Thanks for the help :)

Tracy, I tried your sugestion, but it didn't work :(

As for the cookie idea, that sounds great. I have been thinking about using a SQL database...but I'm not sure how to do the cookie session stuff....or how to delete the cookie after they have logged out (with a script such as logout.cgi or something).

Would you be able to provide me with a few bits of sample code ;-)

Thanks loads

Andy
 
What isn't working about it? Does it return "unknown" or just not return what it should? Or not return anything?
By the way, since I modified that code to return two items of data (it used to only return the userid), the "unknown" line should probably be changed so it returns two items as well:
Code:
return ("unknown","") unless $authtype =~ /basic/i;
Tracy Dryden
tracy@bydisn.com

Meddle not in the affairs of dragons,
For you are crunchy, and good with mustard.
 
Ok, that code works fine now :)

The main thing I need help with now its what I put further up. I have decided that cookies sound like a very good idea. The problem is that .htaccess if not very workable with. I have got nearly 2000 members signed up ion less than 3 weeks, so I think that .htaccess will be a bit streached, thus wanting to now use SQL and cookies. Could you (or someone) please provide example of how to connnect to the database, and also how to do the cookie stuff where it puts a cookie into memory?

Thanks :)

Andy
 
From RFC 2617 --
The most serious flaw in Basic authentication is that it results in the essentially cleartext transmission of the user's password over the physical network...Because Basic authentication involves the cleartext transmission of passwords it SHOULD NOT be used to protect sensitive or valuable information...Basic Authentication is also vulnerable to spoofing by counterfeit servers.

This document has a lot of good reasons why not to use the basic method. Regardless of the fact that the script may be working, I beseech you and anyone else in your shoes to stop.

Okay, that aside, let's look at the method stated in my earlier post. First I'll talk about implementation, then I'll speak on the modification of external conditions, then on potential weaknesses.

To reiterate, we'll use session cookies (those without an explicit expiration that reside in memory until terminated by the web application or terminated by the closing of the user agent) that contain a unique and totally random ID that is bound to user data stored temporarily on the server. The data on the server should include the following:

1. Timestamp: This field should be updated every time a user commits an action within the application. Your script can then check the time between actions and see if it's within an assumed same-user range... Otherwise, the script can proceed to log the user out automatically.

2. IP Address: This will help prevent cookie spoofing, only allowing requests from the IP address that first authenticated. There is a potential problem with having this field though, which is that some proxies auto-change the IP address of users mid-session, so a valid user could get bumped as a spoofer. AOL does this, I believe. Jerks.

3. Realm/Permissions/Groups: These are totally optional, but are certainly useful if you have several different areas within your application that require different permissions.

4. Additional User Info (username, common name, etc.): Again, totally optional, but potentially useful.

5. Unique ID: Necessary to bind the rest of the info to the user's cookie, of course.

Okay, so let's start with authentication. I'm going to assume you don't have SSL available, so we need to find another suitable method of encryption. The first thing that comes to mind are the algorithms used in public key infrastructures, but that's unfeasible because of the large numbers necessary and javascript's inability to handle them. And the math would kill some user's CPU's. So we need some other way. Here's one that I just came up with:

1. Client request login page

2. Server generates a random number/character string, stores it in a database with the IP address of the client then sends the client the page with the random number in a hidden input field.

3. Client enters username and password into the login page and hits the submit button. Before the form is submitted to the server, a javascript routine is called which does the following:
a. encrypts the random number from the server with an arbitrary method (say, DES -- you can find javascript implementations of this on the web) using the password as a key, stores this number in another hidden input field.
b. clears the password from the form.
c. submits the form.

4. Server identifies the username, looks up the password and decrypts the random number with the user's password as the key. It then checks the number against the database using the IP address as a key and if they match the user is authenticated. The number is removed from the database whether or not authentication was successful. If it was not valid, step 2 and all proceeding steps are repeated.

This way, the password is never sent over the wire and the one-time random number should sufficiently prevent any successful cryptanalysis. But, since this is just a method that I devised at 5am on no sleep, it could have holes... If there are any cryptanalysists out there, I'd appreciate feedback.

The validation script should then generate a new random character string for the session ID and create a cookie for the user. You can use the CGI.pm module to do this:
Code:
use CGI qw/ :standard /;
my $cookie = cookie(-name  => 'sessid',
                    -value => $randomnumber,
                   );
print redirect(-location => 'entrypage.html',
               -cookie   => $cookie,
              );
By not including -expires in the cookie() definition, you're making the cookie a temporary one (memory-residing). The session ID should be stored in a sessions table in your database, along with the other attributes mentioned earlier (timestamp, IP address, etc).

Now on each subsequent page request from the user, validate the cookie by checking:

1. Does the session ID exist within the database?

2. Is the timestamp associated with the session ID valid?

3. Is the referer associated with the session ID the same IP address that's in the table?

4. Do the permissions associated with the session ID allow the user to be at this page/perform this action?

etc...

Again, you can use CGI.pm to read the contents of the cookie. Simply access cookie('sessid') as a scalar.

When the user logs out, erase the session from the database table and clear the cookie (CGI.pm again!) by resending the cookie('sessid') with an expires attribute in the past:
Code:
use CGI qw/ :standard /;
my $cookie = cookie(-name    => 'sessid',
                    -expires => oldtime,
                   );
print header(-cookie => $cookie);
And that's it. Now for some of the external modifications you can do.

1. Make sure your database/server is secure. Absolutely no point if having good application security if the underlying layer is junk.

2. If you can, get an SSL certificate... They're not expensive, they're easy to implement, they make users more comfortable and they offer a nice security cushion.

And now for a weakness analysis.

Well, authentication seems secure enough. The cookie is temporary and the server tracks the time between actions, so it seems improbable that a person using the computer after a validated user would maintain that user's credentials. If you track IP addresses, spoofing is impossible ... Wait ... hole #1:

Problem:
The user could spoof the IP address as well as the cookie and bypass this mechanism... this would prevent the hacker from receiving the requested page, but if they're just submitting a form request to do some action and don't require the returned page, this doesn't matter.

Solution:
For any action that involves form submissions, make it a two layer thing. Have the user submit the request and receive a confirm page with a hidden input containing a random number that the server has encrypted with the user's password (this should sound like the auth method above in reverse). When the user receives the page, he/she could type in their password, run the number through a decrypt javascript function, remove the password from the form and submit the original random number back to the server. If this number matches what the server initially came up with, the action is performed. Problem solved. I think. It's getting really late and I'm getting really tired, so again, feedback is appreciated.

Uhm.. I can't think of anything else. So here it is. I think after writing this whole long thing, I should get the opportunity to name it, so meet "BRENS Policy": Bren's Ridiculously Extreme but Nifty Security Policy.

Hope this helps. I think I'll make a FAQ one of these days. In the meanwhile, let's call it an RFC.

brendanc@icehouse.net

... so, anyone miss my long diatribes while I was away? ;-)

... I realize there's not much code in here, but it's all pretty straightforward. Just read the docs for CGI.pm, DBI.pm (for database access) and your choice database server.
 
I don't use basic authentication much, for the reasons mentioned by sophisticate, as well as the very obvious fact that it's really simple to decode (the program above isn't that complex, and most of the code was in the perl faqs). The system I used is called ticket-based authentication. The password and userid are transmitted only ONCE, and once they are validated a session id is assigned to the user. That session id (ticket) is the only thing that is passed back and forth after that, and it's a randomly generated string of 16-20 characters, so it's hard to guess at a valid one (especially since a session expires after one hour of non-use). The session id is checked to see if it is valid and non-expired, the expire time is updated, and the userid is pulled from the session record so we know who the user is. The user and session tables are MySQL. With the perl DBI module it's very easy to interface perl with mysql.
The code I use is custom code, but the techniques are pretty standard. Tracy Dryden
tracy@bydisn.com

Meddle not in the affairs of dragons,
For you are crunchy, and good with mustard.
 
What Tsdragon uses is, indeed, pretty standard. That's essentially the foundation of what I propose above. The items that I think are necessary to consider more heavily though are:

1. Do you need to send the password over the wire at all?

2. Protect against spoofing. Keep in mind that anyone on the same network can throw their ethernet card into promiscuous mode and watch whatever traffic you're sending between the server and your machine -- including cookies -- and anyone with a text editor can manually create a cookie.

The proposal above attempts to address these items and now that I'm a little more awake, I've been trying to analyze it... It still seems reasonably secure, even without SSL and the like.

brendanc@icehouse.net
 
Hmmm, I just went back and reread parts of your proposal above, and you're correct. I hadn't thought about encrypting the password on the client side the way you suggest. I'll have to experiment with implementing that, and the IP address check too. Thanks for the hints (even if it wasn't originally my question). Tracy Dryden
tracy@bydisn.com

Meddle not in the affairs of dragons,
For you are crunchy, and good with mustard.
 
tsdragon,

Well, I wouldn't have written that whole thing if it was for just one person... I hope that more people than you take something from it. But thank you.

Keep the following in mind though:

It's not the password that you're encrypting, it's the random number that the server generates and you're using the password as a key. That way, each time the user authenticates, a different encrypted code is sent.

I imagine this would be even more secure if some assymetrical key system was implemented... That way the random number could be encrypted with the password, but not decrypted with it. Instead, the server might have .. say .. a 512 bit key for the user that decrypts the message. This would make it *really* difficult for someone to cryptanalyze, because instead of finding an 8 or so character key string (which could theoretically be done with a dictionary attack, provided they had sniffed enough random numbers/encryptions), they'd need to find the server's key (impossible with a dictionary). Unfortunately, I'm not *that* good a mathematics... yet. :)

Just a thought.

brendanc@icehouse.net
 
Brendan,

Thanks for the clarification about who (whom?) encrypts what. It was clear in your proposal, but I got dyslexic when I wrote about it.

I'm not that good at higher math either, but I'll check some resources and see what I can find.

Your comments about the problems with the ip address checking (spoofing, changing addresses) gave me something to think about too. I'm not sure I'll include that part, but I'll consider it.

Yes, we did miss your "long diatribes" while you were away (you were away?). I tried to make up for your absence, but I'm not quite as good at it. :)
Tracy Dryden
tracy@bydisn.com

Meddle not in the affairs of dragons,
For you are crunchy, and good with mustard.
 
Actually, check that.

Assymetry would help quite a bit in this situation, but not as much as I thought 5 minutes ago. All the attacker would need to do is a dictionary attack on the encryptions and try to find a collision that yielded the ciphertext.

That's still REALLY hard, especially with small variants in the password like capital letters or numbers, but I guess it's possible... maybe.

brendanc@icehouse.net
 
I took a leave of absence from Tek-Tips for a month or so... those lengthy messages that I'm notorious for writing started to affect my work. But, I recently felt the urge to drop on in and see what was up here... and now, well... this particular thread is just too much fun. :)

brendanc@icehouse.net
 
If I really need security strong enough to protect against THAT level of attack, I'll go with SSL. What I've got already, combined with what you propose, is probably more than strong enough for the average membership-type web site (and miles ahead of basic auth).
Tracy Dryden
tracy@bydisn.com

Meddle not in the affairs of dragons,
For you are crunchy, and good with mustard.
 
I just thought of an idea that would help prevent an dictionary attack too: just lock out the account after 'x' number of invalid passwords. They might be lucky enough to hit it within the first 'x' tries, but chances are against it. Tracy Dryden
tracy@bydisn.com

Meddle not in the affairs of dragons,
For you are crunchy, and good with mustard.
 
That wouldn't matter because they could conduct the entire attack offline. All they would need to do is look at the encryption algorithm embedded in the webpage, sniff a few of the unencrypted random numbers and their respective ciphertexts... then they could just export that data to a textfile and go to work at it.

So the flaws are:

Dictionary attacks during the decryption process are pretty easy and often times quite effective. Make sure you have a good password policy implemented -- numbers in between letters, combinations of upper case and lower case, made up words, etc. Anything that's not in the dictionary the hacker uses won't be cracked. Hopefully.

Assymetrical keys would help this because the decryption is based on a 512/1024/whatever bit key that obviously wouldn't appear in any dictionary and is so long that most brute force attempts will fail miserably.

Then the issue becomes reverse dictionary attacks... Attacks that look for collisions (a sequence of characters that yields the same ciphertext as the hidden key). These are hard, but not nearly impossible. Again, good password policy is the best bet to avoid problems here. And lengthy passwords would really help. If you allow any alphanumeric character in the password, the number of possible combinations for pass length n will be 36^n which gets really big, really fast.

brendanc@icehouse.net
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top