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.