Vorbb 'cuz its fresh!


Post Reply 
 
Thread Rating:
  • 0 Votes - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Tutorial - PHP - Creating a secure login system
Author Message
Burningmace
admin
Administrators

Posts: 336
Joined: May 2009
Reputation: 1
Vorbb Bux: 9171

Thanks: 1
46 thank was given in 18 posts
Post: #1
Tutorial - PHP - Creating a secure login system
This tutorial expects you to know the PHP language to a reasonable level.

A lot of people make huge mistakes when designing and coding a login system. You'd be surprised how many are comprimised by the simplest of mistakes.

First note - where possible use SSL for login pages. You can get a certificate signed by CAcert.org for free.

I'm dealing with a session based login system here, so lets start off with the basics. First we need a secure way to talk to the database server. Obviously we have to store MySQL login details in the script somewhere, but you must NEVER put them in the script doing the queries. The best thing to do is keep the settings in a file outside the document root of the web server. So, if your Apache server points to C:\webdocs, keep them outside that folder. That way there is no way to get hold of them through any exploit found in your website. For extra security, open up php.ini and add the directory to the include_dir etnry. That way, even if the full source of a page is somehow released, an attacker can't find the location of the PHP file that contains the database login information. For example:
PHP Code:
// If you see this, you know where the file is
require('C:/phpincludes/mysql_login.php');
// Whereas the following doesn't give you any clue.
require('mysql_login.php'); 

If you can't mess with php.ini, create a folder called includes and put your login script in there. Then create a file called .htaccess in the folder and put the following in it:
Code:
deny from all
This stops anyone from accessing the files in the folder.

The best way to handle MySQL connections is through a wrapper function.
PHP Code:
<?php
function sql_connect()
{
    
$user "username";
    
$pass "password";
    
$db "dbName";
    
$conn mysql_connect("localhost"$user$pass);
    
mysql_select_db($db);
    unset(
$user);
    unset(
$pass);
    unset(
$db);
    return 
$conn;
}
?>
The wrapper function causes the login details to be inaccessible from other scripts.

Now we've discussed actually connecting to the database, we have to consider how we're going to store passwords in a secure way. You must never store passwords in the database as plain text. The best method of storage is using a hash algorithm. I advise you not to use MD5, as it's no longer considered secure. SHA1 is the best solution right now. When a user logs in, take the password they entered and hash it, then compare the hash to the one in the database for that user. If the hashes match, the password is correct.
Now, there's the problem with hashes on their own. People have created massive look-up tables for hashes, in which you can enter a hash and find the plain text value it corresponds to. This means that if the hash is ever leaked from the database in any way, there's a good chance that the password can be recovered. Furthermore, if the password is short (5-6 letters) a password cracker can recover it less than a day, even if it is random and alphanumeric. So, we use a process called salting to fix this problem. Salting involves appending a large string to a password before hashing it. If you know the salt and the password, you can generate the same hash. However, the salt prevents lookup tables from working and causes password cracking to take hundreds (if not thousands) of years to complete. You can safely store this hash value pretty much anywhere. The best practise is store a different salt for each username and store it in the database.
PHP Code:
<?php
$username 
mysql_escape_string($_POST['user']);
$password $_POST['pass'];
$result mysql_query("SELECT salt FROM users WHERE username = '{$username}'");
if(
mysql_num_rows($result) == 0)
{
    
mysql_free_result($result);
    
// user doesn't exist
    
die("Bad username.");
} else {
    
$row mysql_fetch_array($resultMYSQL_ASSOC);
    
$salt $row['salt'];
    
$pwh sha1($password $salt);
    
mysql_free_result($result);
    
$result mysql_query("SELECT username FROM users WHERE username = '{$username}' AND passhash = '{$pwh}'");
    if(
mysql_num_rows($result) == 1)
    {
        
// successful login
    
} else {
        
// bad password
    
}
}
?>
I personaly use two salts stored in the database, one before the password and one after the password. This causes the most and least significant (i.e. first and last) bytes of the plaintext to be independant of the password. This reduces the likelyhood that any vulnerability found in SHA1 in the future would allow a successful recovery of the password. Salts should be at least 20 letters long, but the longer they are the better. I tend to use 100+ character strings with symbols, numbers, lower and upper case letters. You must always remember to use mysql_escape_string to filter any user input.
Now, this is still no use if someone can send millions of login attempts to your login page. This is where a CAPTCHA comes in. PHP has a built-in image library called GD, which you can use to create these images. Here's my CAPTCHA script:
PHP Code:
<?php
session_start
();

header('Content-Type: image/png');
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); 
header("Last-Modified: " gmdate("D, d M Y H:i:s") . " GMT"); 
header("Cache-Control: no-store, no-cache, must-revalidate"); 
header("Cache-Control: post-check=0, pre-check=0"false);
header("Pragma: no-cache"); 

require_once(
'includes/functions.php');
// This is from functions.php
// It generates a random 5 letter string. You can write your own.
$cs GenerateCaptchaString();

$_SESSION['captcha'] = $cs;

$cn mt_rand(1,7);
// Pick a random background image.
$image imagecreatefromgif('captcha' $cn '.gif');

imagefilter($imageIMG_FILTER_BRIGHTNESS30);

$black imagecolorallocate($image000);
$white imagecolorallocate($image255255255);

$ti imagecreatetruecolor(6030);
$ti_black imagecolorallocate($ti000);
$ti_green imagecolorallocate($ti02550);
// Fill the background with green (later used as a transparency key)
imagefill($ti55$ti_green);
// Write the string to the image
imagestring($ti5mt_rand(4,5), mt_rand(1,3), $cs$ti_black);
$tl imagecreatetruecolor(20080);
$tl_green imagecolorallocate($tl02550);
// Set the transparency key to green
imagecolortransparent($tl$tl_green);
// Resize the image
imagecopyresized($tl$ti00002501006030);
// Merge the image with the background
imagecopymerge($image$tl000020080100);

// Draw random horizontal white lines
for($i 0$i 5$i++) {
    
imageline($image0mt_rand(0,100), 250mt_rand(0,100), $white);
}

// Draw random horizontal black lines
for($i 0$i 5$i++) {
    
imageline($image0mt_rand(0,100), 250mt_rand(0,100), $black);
}

// Use wave distortion to make the CAPTCHA harder to break
wave_area($image00250100520);

// Draw vertical lines down the image
$b mt_rand(0,10);
for(
$x $b$x 250$x += 30)
{
    
imageline($image$x0$x100$black);
    
imageline($image$x 10$x 1100$black);
}

// Draw small random letters across the image
imagestring($image5mt_rand(15), mt_rand(13), sha1(mt_rand(0100000)), $black);
imagestring($image5mt_rand(15), mt_rand(5055), sha1(mt_rand(0100000)), $black);

// Dump the image to the output stream
imagepng($image);

function 
wave_area($img$x$y$width$height$amplitude 10$period 10) {
    
// Make a copy of the image twice the size
    
$height2 $height 2;
    
$width2 $width 2;
    
$img2 imagecreatetruecolor($width2$height2);
    
imagecopyresampled($img2$img00$x$y$width2$height2$width$height);
    if(
$period == 0$period 1;
    
// Wave it
    
for($i 0$i < ($width2); $i += 2)
        
imagecopy($img2$img2$x $i 2$y sin($i $period) * $amplitude$x $i$y2$height2);
    
// Resample it down again
    
imagecopyresampled($img$img2$x$y00$width$height$width2$height2);
    
imagedestroy($img2);

?>
If you put this on your login page, it will store the answer in the "captcha" session variable. You can compare this to the value they entered. You must only check if the username/password is ok after you verify that the captcha variables match.

Now, when you've got yourself a secure login, you need to make sure that the session stays authenticated. If someone steals a valid user's session, it could allow them to gain unauthorized access to the site. There are several things we can do to remedy this. First, we can set a random value in a cookie and keep the same value in a session variable. This stops most automated session cookie stealing software from working.
We can also lock each session to the IP address it was started from. However, this causes a problem. We can't lock to a single IP address because a lot of people have a dynamic IP that refreshes after a certain period of time. If one of these refreshes occurs during a session, the session will be invalidated. So, we lock to the first three octets of the IP address. If the IP is 152.65.201.14, we lock to 152.65.201.xxx
When getting the IP address we can't just check $_SERVER['REMOTE_ADDR'], we have to check $_SERVER['HTTP_FORWARDED_FOR'] too in case the client is using a proxy. Normally, people just take the value of HTTP_FORWARDED_FOR as the real IP if it's supplied. But consider this - there's nothing to stop an attacker writing a program to supply the HTTP_FORWARDED_FOR header with absolutely any value they wish with every request, even though they're not using a proxy. So, we must lock to both REMOTE_ADDR and HTTP_FORWARDED_FOR. Another thing we can do is lock to the browser signature using HTTP_USER_AGENT. This helps stop session theft too.
These checks should be made upon every request for any page within the secure area. You can make this easier by using a require() include.

So, that's it! You now know how to create a secure login system in PHP.

[Image: 29fxcgo.jpg]
Professional web design
"Why hello there Sir, might I perhaps take part in eating your brains?"
06-13-2009 07:06 AM
Visit this user's website Find all posts by this user Quote this message in a reply
Thank given by Blindkilla
Sponsors

Vorbb Founder
Administrators

Posts: 344
Joined: May 2009
Reputation: 3
Vorbb Bux: 3258

Thanks: 18
88 thank was given in 29 posts
Post: #2
RE: Tutorial - PHP - Creating a secure login system
Amazing tutorial burningmace, a lot of great information with details here! This will be very useful.

[Image: 2ag4013.jpg]
06-13-2009 11:02 AM
Visit this user's website Find all posts by this user Quote this message in a reply
Post Reply 


Forum Jump:


Contact Us - Vorbb - Return to Top - Return to Content - Lite (Archive) Mode - RSS Syndication