I have some small applications that I want to secure. I've been using the following setup that I think is fairly safe, but I've never been able to set my mind at ease that it really is. Could you give me some reviews on the security of this? It doesn't need super-security like credit card data, but I suppose secure is secure.
Cookie-based Sessions. User table is:
- Username field (cleartext)
- Random/unique salt field (created with
mt_rand()
at signup - Password field (SHA256 hash)
- (among other stuff)
Login method takes username, looks for the DB row, gets the salt, adds it to the end of the posted password, calcs a SHA256 hash for that string, and compares that to the password field in the DB.
auth.php include at beginning of app
<?php
session_start();
if(!isset($_GET['logout']) && isset($_SESSION['user']) && $_SESSION['ipadd'] == $_SERVER['REMOTE_ADDR']) {
// currently logged in
// setup data
require_once('lib/functions.php');
$db = new ezSQL_sqlite('./','main.db');
$user = new user($db,$_SESSION['user']);
return;
}
// build login form
$head = '<html><head><title>Please login</title>
<style type="text/css">h2 { margin-top: 75px; margin-left: 100px; }</style>
</head><body>';
$form = '<form method="post" action="'.$_SERVER['PHP_SELF'].'" id="userLogin">
<table><tr>
<td>UserName:</td><td><input type="text" name="username" value="'.$_POST['username'].'"></td>
</tr><tr>
<td>Password:</td><td><input type="password" name="pass" /></td>
</tr><tr>
<td>Remember me on this computer</td>
<td><input type="checkbox" name="remember" value="1" /></td>
</tr><tr>
<td> </td>
<td><input type="hidden" name="loggingin" value="true" />
<input type="submit" value="login" />
</td></tr></table>
</form></body></html>';
$msg[1] = '<h2>You\'ve logged out.</h2>';
$msg[3] = '<h2>That username and password didn\'t match anything on record.</h2>';
$msg[4] = '<h2>You must login to use this application</h2>';
// used logout button
if($_GET['logout'] == 'true') {
setcookie('SaveMe','',time()-3600);
session_unset();
die($head.$msg[1].$form);
// trying to login from form or returning with 'save me' cookie
} elseif ($_POST['loggingin'] == 'true' || isset($_COOKIE['SaveMe'])) {
require_once('lib/functions.php');
$db = new ezSQL_sqlite('./','main.db');
$loginName = (isset($_POST['username'])) ? $_POST['username'] : $_COOKIE['worshipSaveMe'];
// ADDED: escaping of posted/cookie data;
$loginName = mysql_real_escape_string($loginName);
// try to create new user object, error on fail
try {
$user = new user($db,$_POST['username']);
} catch (Exception $e) {
die($head.'<h2>'.$e->getMessage().'</h2>'.$form);
}
// try to login with user object, die on fail
if( ! $user->login($_POST['pass']))
die($head.$msg[3].$form);
else {
// if remember me box was checked
if($_POST['remember'] == 1)
setcookie('worshipSaveMe',$_POST['username'],time()+60*60*24*365);
return;
}
// no post data, no save me cookie, just got here
} else {
die($head.$msg[4].$form);
}
And the relevant part of the user class:
class user
{
private $username;
protected $ID;
private $created;
private $salt;
private $password;
private $db;
function __construct ( ezSQL_sqlite $db, $postedUsername ) {
if( !$user = $db->get_row("SELECT * FROM users WHERE uname = '$postedUsername';"))
throw new Exception ('That username didn\'t match anything on record.');
else {
$this->db = $db;
$this->username = $postedUsername;
$this->ID = $user->user_id;
$this->created = date('Y-m-d',$user->createdDate);
$this->salt = $user->salt;
$this->password = $user->pword;
}
}
/** check password for match
* @param user input from a posted form
* @return boolean
*/
private function verifyPassword($postedPass) {
$pHash = hash('sha256',$postedPass . $this->salt);
if($this->password != $pHash)
return FALSE;
else
return TRUE;
}
/** This relies on cookie based sessions, and so
* must be called before any output to browser.
* @return bool
*/
public function login($postedPassword) {
if( ! $this->verifyPassword($postedPassword) ) {
return FALSE;
} else {
if( isset( $_COOKIE["PHPSESSID"] )) { //be sure session was initialized
$_SESSION['user'] = $this->username;
$_SESSION['ipadd'] = $_SERVER['REMOTE_ADDR'];
return TRUE;
} else {
die('Session must be initialized before calling login method.');
}
}
}
hash('sha256',$password)
function withcrypt($password,'$2a$12$1234567890123456789012$')
(obviously with a different salt string). \$\endgroup\$