SECURING PHP USER AUTHENTICATION, LOGIN, AND SESSIONS
SECURING PHP USER AUTHENTICATION, LOGIN, AND SESSIONS
Many, if not all, of you have had to deal with creating a secure site login at some point in time. Although there are numerous articles written on the subject it is painstakingly difficult to find useful information from a single source. For this reason I will be discussing various techniques I have used or come across in the past for increasing session security to hinder both session hijacking and brute force password cracking using Rainbow tables or online tools such as GData. I use the word hinder due to the fact no foolproof methods exist for preventing session hijacking or brute force cracking, merely increasing degrees of difficulty. Choose a method wisely based on your site’s current or anticipated traffic, security concerns, and intended site usage. The following examples have been coded using PHP and MySQL. I more than willingly accept comments, suggestions, critiques, and code samples from readers like you as they benefit the community on the whole.
Update: Security Concerns with Hashing Algorithms
There are some inherent security considerations to take into account when using very fast hashing algorithms such as SHA or MD5. Modern day, multi-processor computers and GPUs can quickly brute-force passwords that aren’t encrypted with a very slow, secure algorithm. For these reasons, it is recommended you do not use these and instead use bcrypt encryption or sha-256/512 with key stretching. In the near future there will be a post containing this updated method for secure authentication.
The One-Way Password Hashing Algorithm
123456789101112131415161718192021222324252627282930313233343536373839404142 |
|
The password validation function
123456789101112 |
|
Below this line are older, obsolete methods for securing your data. I do not recommend using the password hashing methods outlined below, but the general ideas and concepts still apply.
Method 1: Hashed Password, Unique Salt
The first method involves storing a unique salt in one of your configuration files, a define statement, or class constant. Salting your passwords prior to hashing (MD5, SHA1, SHA256, Whirpool, etc.) hinders such attacks by increasing the amount of storage and computation required to crack your password. For a full listing of supported hashing algorithms, take a look at hash_algos. The primary concern with a singular unique salt for any number of stored passwords is that once figured out, the salt becomes utterly useless.
Part 1: The MySQL Table
1234567891011 |
|
Part 2: The One-Way Password Hashing Algorithm
123456789 |
|
Part 3: The password validation function
1234567891011121314 |
|
Method 2: Hashed Password, Random Salt
The second method utilizes a more secure form of salt, the random salt. The random salt is generated upon account creation and is unique to that account. The benefit of using random salts is that a compromised account will have no adverse effect on the remainder of accounts due to the uniqueness of the salts on a per user basis. A good salt should incorporate letters, numbers, and symbols and preferably be over 8 characters in length.
Although this method is still more secure than the first, it may have a negative impact of requiring you to perform an extra database query to grab secure data after validating the session. The first query performs a lookup of the uid, password, and salt based on either a username or email address. The second query would generally be performed if you required further data that was not contained within your user login table. This query would more likely utilize a join on related related tables to gather further information. Benefits of utilizing a random salt
Part 1: The MySQL Table
123456789101112 |
|
Part 2: The pseudo-random salt generator
12345678910 |
|
Part 3: The One-Way Password Hashing Algorithm
123456789 |
|
Part 4: The Password Validation Function
12345678910111213 |
|
Method 3: Hashed Password, UNIX Timestamp Based Salt
Assuming your database becomes compromised, it would be very easy for an attacker to piece together your encryption algorithm if your user login table contains a field named salt. This method provides an abstracted way of hashing your password based on a substring of the last modified date of your user login entry. You may consider storing your user’s created date as a TIMESTAMP as opposed to an UNSIGNED INT because of the Year 2037 Bug. This may seem a little ridiculous to assume my PHP scripts will still be running in 2037, but then again nobody readily anticipated the Y2K Bug either. The benefit of using a pre-existing, non-changing field in your user table as a salt is that it adds obscurity. If your database is compromised, it is not readily apparent that your passwords are salted and there is no indication of how they were salted. If you do choose to go the UNSIGNED INT route with your user’s createed date, I recommend doing some obfuscation of the date as your salt (i.e. using every odd digit, reversing the integer, etc.).
Part 1: The MySQL Table
12345678910111213 |
|
Part 2: The One-Way Password Hashing Algorithm
12345678910111213 |
|
Part 3: The Password Validation Function
1234567891011121314 |
|
IP Address Validation
IP address validation relies on the fact that the majority of users will maintain a static IP over the duration of their site visit. On each page load we would be performing a comparison for equality on the visitor’s current IP and the stored copy of their IP in the session. Drawbacks from this method are related to the fact that many individuals do not have a static IP. Some ISP providers lease IP addresses for a given amount of time before expiration (generally for the duration of their online session) while others may utilize numerous proxies.
One solution you can implement to alleviate the static IP issue is to take a substring of the IP address and use the the first 3/4 of an IPv4 address (24 bits / 3 bytes / third octet). If validation fails utilizing this method logout will be enforced and the user will be required to log back in, thereby updating their IP address. Please note that this method may be bypassed by IP spoofing.
I have already posted an article on retrieving a client’s ip so I will not go over that here. Instead, I will simply demonstrate an easy method for dropping the fourth octet.
1234567891011121314151617 |
|
To enhance session security utilizing IP address checking, follow these steps:
- Obtain the user’s IP address and run it through the trimIP() function on page load.
- Check the IP address against a session variable, i.e. $_SESSION['user_ip'], to see if they match.
- If the new IP address does not match the session IP and the session IP address is not empty, invalidate the current logged in user’s session.
For the sake of not leaving anything out, here’s a proof of concept for validating IP addresses:
123456789101112131415161718192021 |
|
User Agent Validation
Many individuals prefer validating the user’s browser agent as opposed to their IP address because the information should remain static over the duration of the site visit. There exist a plethora of different user agent strings depending on both your browser, operating system, and versioning. An example user agent would be:
Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.10) Gecko/2009042316 Firefox/3.0.10 (.NET CLR 3.5.30729)
Since user agents are browser dependent and a user’s session is only valid within their currently opened browser, validating the user agent is a great way to enhance security of your site. Below is a quick proof of concept implementation on how to go about adding a user agent validation check to your existing session handling:
123456789101112131415161718192021 |
|
As a wrap up, I would ultimately recommend incorporating both user agent validation as well as IP address validation into my session/authentication handling.
Comments
Post a Comment