Jump to content

Check login code security


dptr1988

Recommended Posts

I have a sample login system that I'm using as an example in a tutorial. I was wondering if somebody could go over it and see if they find any security related bugs or any other undesirable features. In fact, any type of criticism would be appreciated.

 

The only reason I'm asking this is because I emphasize security in my tutorial, and it would be very embarrassing if my example code had a serious security flaw!

 

Here is the code:

<?php
session_start();


// Make sure that these are stored in a secure place
// where nobody will find them
$database_salt = 'a@pu#qz$kv%!1&2*(2_3233l;k345{[}`235jsd ';
// Who ever heard of putting salt on your cookies?
$cookie_salt = 'adh;234509()_*!#$HKsdMN$35Dfh-9ihsdfk';


function hash_password($password, $salt)
{
$counter = strlen($password);
$c2 = strlen($salt);

if ($c2 < $counter)
{
  $counter = $c2;
}

$salted_string = '';

for ($i = 0; $i < $counter; $i++)
{
  $salted_string .= $password[$i] . $salt[$i];  
}

return md5($salted_string . $salt);
}

function no_login($error_message)
{
// Make sure that they are logged out.
unset($_SESSION['login_id']);
setcookie('login_username', '', time()-60*60, '/', 'example.com');
setcookie('login_password', '', time()-60*60, '/', 'example.com');

echo "<html><head><title>login required</title></head>" . 
  "<body><p>{$error_message}</p>" .
  "<form action='{$_SERVER['REQUEST_URI']}' method='post'>" .
  "<p>Username: <input type='text' name='login_username' value='' ></p>" .
  "<p>Password: <input type='password' name='login_password' value='' ></p>" .
  "<p>Remember me <input type='checkbox' name='login_usecookie' value='' ></p>" .
  "<input type='submit' name='login_submit' value='Login' >" .
  "</form></body></html>";
exit();
}



// Assumes that you have already setup the database connection 
// somewhere else in the file
function db_mysql($username)
{

$login_info = array();

$username = mysql_real_escape_string($username);

$query = "SELECT userid, username, password, user_permissions " .
  "FROM login_users WHERE username = '{$username}' LIMIT 1";
$result = mysql_query($query);
if ($result === false)
{
  $login_info['success'] = false;
  $login_info['message'] = "ERROR: Unable to access user information!";
  return $login_info;
}

$data = mysql_fetch_assoc($result);
if ($data === false)
{
  $login_info['success'] = false;
  $login_info['message'] = "Invalid username.";
  return $login_info;
}

$login_info['success'] = true;
$login_info['id'] = $data['userid'];
$login_info['permissions'] = $data['user_permissions'];
$login_info['password_type'] = 'db_hashed';
$login_info['password'] = $data['password'];
return $login_info;

}

function db_array($username)
{
$users_passwords = array( 
  array('joe_sample', 'password', 'r|w|e'),
  array('john_sample', 'password', 'r'),
  array('harry', 'asdf', 'r|w|e|d|c'),
  array('test', 'test_password', 'r|w|c')
);

foreach($users_passwords as $id=>$info)
{
  if ( $info[0] == $username)
  {
   return array( 'success' => true, 'id' => $id,
    'permissions' => $info[2],
    'password_type' => 'plain_text',
    'password' => $info[1] );
  }
}
// We didn't find anybody by that name in the list  
return array( 'success' => false,   
  'message' => 'Invalid username or password.' );
}

// This loads a serialized array from a file and 
// passes it on to db_array()
function db_serialized_array($username)
{
$password_file = '/path/to/password/file.dat';
$data = file_get_contents($password_file);

if ($data === false)
{
  return array('success' => false, 
   'message' => 'ERROR: User database not found.');
}

$user_list = unserialize($data);
if ($user_list === false)
{
  return array('success' => false, 
   'message' => 'ERROR: Unrecognized user database format.');
}

foreach($user_list as $id=>$info)
{
  
  if ( $info[0] == $username)
  {
   return array( 'success' => true, 'id' => $id,
    'permissions' => $info[2],
    'password_type' => 'db_hashed',
    'password' => $info[1] );
  }
  
}

// We didn't find anybody by that name in the list  
return array( 'success' => false,   
  'message' => 'Invalid username or password.' );

}

// This function takes a username/password combination and 
// checks it against the database If it's correct, it will
// return information about the user, otherwise it will
// print out the login form with an error message
function do_login($username, $password_info)
{
global $cookie_salt, $database_salt;

// Here you can select the database that you want
// to use for the user logins
//$result = db_serialized_array($username);
$result = db_array($username);
//$result = db_mysql($username);
  
if (!$result['success'])
{
  no_login($result['message']);
}

$db_pw_type = $result['password_type'];
$usr_pw_type = $password_info['password_type'];


$passwords_matched = ( 
  ($usr_pw_type == 'plain_text'    AND $db_pw_type == 'plain_text' 
   AND $result['password'] == $password_info['password']
  ) 
  OR
  ($usr_pw_type == 'cookie_hashed' AND $db_pw_type == 'plain_text' 
   AND hash_password($result['password'], $cookie_salt) == 
   $password_info['password']
  ) 
  OR
  ($usr_pw_type == 'plain_text'    AND $db_pw_type == 'db_hashed'  
   AND $result['password'] ==  hash_password(
   $password_info['password'], $database_salt)
  ) 
  OR
  ($usr_pw_type == 'cookie_hashed' AND $db_pw_type == 'db_hashed'  
   AND hash_password($result['password'], $cookie_salt) == 
   $password_info['password'])
);

if (!$passwords_matched)
{
  no_login('Wrong password');
}

$permissions = explode('|', $result['permissions']);

$_SESSION['login_id'] = $result['id']; 
$_SESSION['login_username'] = $username;
$_SESSION['login_permissions'] = $permissions;

}


function check_permissions($permissions_required)
{
if (!in_array($permissions_required, 
  $_SESSION['login_permissions'])
)
{
  echo "You need permission {$permissions_required} " .
   "to access this page";
  exit();
}
}


// Are they wanting to log out?
if (isset($_GET['log_out']) AND $_GET['log_out'] == 1)
{
$_SERVER['REQUEST_URI'] = $_SERVER['PHP_SELF'];
no_login('You are now logged out');
}


// Always allow them to login, even if they are already logged in
if (isset($_POST['login_submit']))
{

$username = trim($_POST['login_username']);
$password = trim($_POST['login_password']);
do_login($username, array('password_type' => 'plain_text',
  'password' => $password) );

// if do_login() returns, then the user has logged in

// If the user wants a cookie, give him one
if (isset($_POST['login_usecookie']))
{
  $cookie_password = hash_password(hash_password($password, 
   $database_salt), $cookie_salt);
  setcookie('login_username', $username, time()+60*60*24*30,
   '/', 'example.com');
  setcookie('login_password', $cookie_password, time()+60*60*24*30, 
   '/', 'example.com');
}

header("Location: http://{$_SERVER['HTTP_HOST']}" .
  "{$_SERVER['REQUEST_URI']}");

}

// If not logged in
if (!isset($_SESSION['login_id']))
{
// Check for a cookie
if (isset($_COOKIE['login_username']) AND 
  isset($_COOKIE['login_password']) 
)
{
  do_login($_COOKIE['login_username'], array('password_type' => 
   'cookie_hashed', 'password' => $_COOKIE['login_password']));
}
else
{
  no_login("Please login to view this page.");
}

}

check_permissions('d');


// Create an log out link that can be used later in the page
$log_out_link = "<a href='{$_SERVER['PHP_SELF']}?log_out=1'>Log Out</a>";

echo "You are now in a protected page, logged in as {$_SESSION['login_username']} " .
"and have the required permission 'd'\n\n\n{$log_out_link}";

?>

Link to comment
Share on other sites

  • 3 weeks later...

Ok, I have a copy of this script installed at 'http://www.phpcodinghelp.com/dev/login_system/login.php'. It is using the mysql database backend and all of the users that are in the array in the 'db_array' function have been added to the database.

 

Note: only user 'harry' has the required permission 'd' that is needed to access the page. All the other can just login.

 

Username: 'harry'

Password: 'asdf'

 

Other username/password pairs without the needed permissions:

joe_sample/password

john_sample/password

test/test_password

 

Username and password hashes provided for your convienience. The are the password hashes from the database.

joe_sample/f6b363d47c1ff60c67eb121d6fb101c5

john_sample/f6b363d47c1ff60c67eb121d6fb101c5

harry/434e2ffa425d98d9682d2cef6a4a0a10

test/32e0b42b8a18365e9975b402a5d9d150

 

Expected behaviour:

If you are not logged in, you will be presented with the login form.

If you have posted incorrect values in the login form, you will be given an error message and the login form again.

If you successfully login and have the required permissions, you should see this message: "You are now in a protected page, logged in as X and have the required permission 'Y' ", where 'X' is replaced with the username, and 'Y' with the permission that the page requires.

If you select 'Remember me' when you login, you should receive a cookie with a username and password that will allow you to login without having to type your username/password combination.

 

Note: This script is hosted on my own server, so feel free to do anything you want to the server. ( I make regular backups, so you can even erase my hard drive if you are able to!!).

 

Thank you all for your help!

 

 

Edit reason: To correct the password hashes.

 

Link to comment
Share on other sites

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.