Hello gamedev
I'm currently trying to implement a secure login script using https, mysql and sesssions. I've been following this guide from here to manage sessions:
http://www.wikihow.com/Create-a-Secure-Session-Managment-System-in-PHP-and-MySQL
My problem is that my session data is not being stored from my login script (in logindb.php). The session_id is changing on every single page load and encrypts the data coming in/out to db and I'm not sure if this is whats causing the data to not carry over to next page load.
I've been debugging for a while now and cannot seem to come to a solution. I think maybe I just don't understand how sessions work. Sorry for the long post, I've included my code below for anyone who wants to help me look over it for any issues.
Much appreciated
./inc/logindb.php
<?php
require_once('session.class.php');
$session = new session();
$session->start_session('_s', true);
function login($email, $password, $mysqli) {
// Using prepared Statements means that SQL injection is not possible.
if ($stmt = $mysqli->prepare("SELECT uid, username, password, salt FROM login_ids WHERE email = ? LIMIT 1")) {
$stmt->bind_param('s', $email); // Bind "$email" to parameter.
$stmt->execute(); // Execute the prepared query.
$stmt->store_result();
$stmt->bind_result($uid, $username, $db_password, $salt); // get variables from result.
$stmt->fetch();
//echo "session: " . session_id() . "
";
//echo "uid: " . $uid . "
";
//echo "pw: " . $password . "
";
//echo "db: " . $db_password . "
";
$password = hash('sha512', $password.$salt); // hash the password with the unique salt.
//echo "salt: " . $salt . "
";
//echo "pw-salt: " . $password . "
";
if($stmt->num_rows == 1) { // If the user exists
// We check if the account is locked from too many login attempts
if(checkbrute($uid, $mysqli) == true) {
// Account is locked
// Send an email to user saying their account is locked
return false;
} else {
if($db_password == $password) { // Check if the password in the database matches the password the user submitted.
// Password is correct!
$user_browser = $_SERVER['HTTP_USER_AGENT']; // Get the user-agent string of the user.
$uid = preg_replace("/[^0-9]+/", "", $uid); // XSS protection as we might print this value
$_SESSION['uid'] = $uid;
$_SESSION['username'] = $username;
$_SESSION['login_string'] = hash('sha512', $password.$user_browser);
// Login successful.
return true;
} else {
// Password is not correct
// We record this attempt in the database
$now = time();
$mysqli->query("INSERT INTO login_attempts (uid, time) VALUES ('$uid', '$now')");
return false;
}
}
} else {
// No user exists.
return false;
}
}
}
function checkbrute($uid, $mysqli) {
$valid_attempts = time() - (2 * 60 * 60);
if ($stmt = $mysqli->prepare("SELECT time FROM login_attempts WHERE uid = ? AND time > '$valid_attempts'")) {
$stmt->bind_param('i', $uid);
// Execute the prepared query.
$stmt->execute();
$stmt->store_result();
// If there has been more than 5 failed logins
if($stmt->num_rows > 5) {
return true;
} else {
return false;
}
}
}
function login_check($mysqli) {
// Check if all session variables are set
if(isset($_SESSION['uid'], $_SESSION['username'], $_SESSION['login_string'])) {
$uid = $_SESSION['uid'];
$login_string = $_SESSION['login_string'];
$user_browser = $_SERVER['HTTP_USER_AGENT']; // Get the user-agent string of the user.
if ($stmt = $mysqli->prepare("SELECT password FROM login_ids WHERE uid = ? LIMIT 1")) {
$stmt->bind_param('i', $uid); // Bind "$user_id" to parameter.
$stmt->execute(); // Execute the prepared query.
$stmt->store_result();
if($stmt->num_rows == 1) { // If the user exists
$stmt->bind_result($password); // get variables from result.
$stmt->fetch();
$login_check = hash('sha512', $password.$user_browser);
if($login_check == $login_string) {
// Logged In!!!!
return true;
} else {
// Not logged in
return false;
}
} else {
// Not logged in
return false;
}
} else {
// Not logged in
return false;
}
} else {
// Not logged in
return false;
}
}
?> ./inc/session.class.php
// file - session.class.php
<?php
class session {
function start_session($session_name, $secure) {
// Make sure the session cookie is not accessable via javascript.
$httponly = true;
// Hash algorithm to use for the sessionid. (use hash_algos() to get a list of available hashes.)
$session_hash = 'sha512';
// Check if hash is available
if (in_array($session_hash, hash_algos())) {
// Set the has function.
ini_set('session.hash_function', $session_hash);
}
// How many bits per character of the hash.
// The possible values are '4' (0-9, a-f), '5' (0-9, a-v), and '6' (0-9, a-z, A-Z, "-", ",").
ini_set('session.hash_bits_per_character', 5);
// Force the session to only use cookies, not URL variables.
ini_set('session.use_only_cookies', 1);
// Get session cookie parameters
$cookieParams = session_get_cookie_params();
// Set the parameters
session_set_cookie_params($cookieParams["lifetime"], $cookieParams["path"], $cookieParams["domain"], $secure, $httponly);
// Change the session name
session_name($session_name);
// Now we cat start the session
session_start();
// This line regenerates the session and delete the old one.
// It also generates a new encryption key in the database.
session_regenerate_id(true);
}
function __construct($secure = false) {
// set our custom session functions.
session_set_save_handler(array($this, 'open'),
array($this, 'close'), array($this, 'read'),
array($this, 'write'), array($this, 'destroy'),
array($this, 'gc'));
// This line prevents unexpected effects
// when using objects as save handlers.
register_shutdown_function('session_write_close');
}
function open() {
$host = 'localhost';
$user = 'sec_user';
$pass = 'f3HZqVGADvYsnvjA';
$name = 'secure_sessions';
$session_mysqli = new mysqli($host, $user, $pass, $name);
$this->db = $session_mysqli;
return true;
}
function close() {
$this->db->close();
return true;
}
// prepare statement (read_stmt) to be executed on db
// param: $id - session id stored in db
function read($id) {
if(!isset($this->read_stmt)) {
$this->read_stmt = $this->db->prepare("SELECT data FROM sessions WHERE id = ? LIMIT 1");
}
$this->read_stmt->bind_param('s', $id);
$this->read_stmt->execute();
$this->read_stmt->store_result();
$this->read_stmt->bind_result($data);
$this->read_stmt->fetch();
$key = $this->getkey($id);
echo "read: \"" . $data . "\"
decrypted: ";
$data = $this->decrypt($data, $key);
echo $data . "
";
return $data;
}
// prepare statement (w_stmt) to be executed on db
function write($id, $data) {
if ($data == null)
return false;
// Get unique key
$key = $this->getkey($id);
// Encrypt the data
echo "write: " . $data . "
crypted: ";
$data = $this->encrypt($data, $key);
echo $data . "
";
$time = time();
if(!isset($this->w_stmt)) {
$this->w_stmt = $this->db->prepare("REPLACE INTO sessions (id, set_time, data, session_key) VALUES (?, ?, ?, ?)");
}
$this->w_stmt->bind_param('siss', $id, $time, $data, $key);
$this->w_stmt->execute();
return true;
}
// delete session
function destroy($id) {
if(!isset($this->delete_stmt)) {
$this->delete_stmt = $this->db->prepare("DELETE FROM sessions WHERE id = ?");
}
$this->delete_stmt->bind_param('s', $id);
$this->delete_stmt->execute();
return true;
}
//garbage collector
function gc($max) {
if(!isset($this->gc_stmt)) {
$this->gc_stmt = $this->db->prepare("DELETE FROM sessions WHERE set_time < ?");
}
$old = time() - $max;
$this->gc_stmt->bind_param('s', $old);
$this->gc_stmt->execute();
return true;
}
//get key from db
//creates key if no key avail
private function getkey($id) {
if(!isset($this->key_stmt)) {
$this->key_stmt = $this->db->prepare("SELECT session_key FROM sessions WHERE id = ? LIMIT 1");
}
$this->key_stmt->bind_param('s', $id);
$this->key_stmt->execute();
$this->key_stmt->store_result();
//check to see if result ret any rows
if($this->key_stmt->num_rows == 1) {
$this->key_stmt->bind_result($key);
$this->key_stmt->fetch();
return $key;
} else {
$random_key = hash('sha512', uniqid(mt_rand(1, mt_getrandmax()), true));
return $random_key;
}
}
public function __isset($var){
return isset($_SESSION[$var]);
}
private function encrypt($data, $key) {
$salt = 'cH!swe!retReGu7W6bEDRup7usuDUh9THeD2CHeGE*ewr4n39=E@rAsp7c-Ph@pH';
$key = substr(hash('sha256', $salt.$key.$salt), 0, 32);
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$encrypted = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $data, MCRYPT_MODE_ECB, $iv));
return $encrypted;
}
private function decrypt($data, $key) {
$salt = 'cH!swe!retReGu7W6bEDRup7usuDUh9THeD2CHeGE*ewr4n39=E@rAsp7c-Ph@pH';
$key = substr(hash('sha256', $salt.$key.$salt), 0, 32);
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, base64_decode($data), MCRYPT_MODE_ECB, $iv);
return $decrypted;
}
}
?> ./inc/header.php
<?php
//file - header.php
include 'db_connect.php';
include 'logindb.php';
require_once('session.class.php');
$session = new session();
if (!session_id())
{
if ($_SERVER["HTTPS"] != "on")
$session->start_session('_s', true);
else
$session->start_session('_s', false);
}
$_SESSION['something'] = 'a value';
echo session_id() . " : ";
print_r($_SESSION);
?>
<html>
<head>
<title>PHP Login Example</title>
<style>
<?php
include './css/header.css';
include './css/signin.css';
?>
</style>
</head>
<body>
./inc/process_login.php
<?php
include 'logindb.php';
include 'db_connect.php';
if (!session_id())
session_start();
if(isset($_POST['email'], $_POST['password'])) {
$email = $_POST['email'];
$password = $_POST['password']; //not hashed due to ssl
if(login($email, $password, $mysqli) == true) {
// Login success
//echo 'Success: You have been logged in!';
header('Location: ../index.php');
} else {
// Login failed
header('Location: ../login.php?error=1');
}
} else {
// The correct POST variables were not sent to this page.
echo 'Invalid Request';
}
?>
./login.php
<?php
include './inc/header.php';
?>
<?php
//check to see if logged in
if(isset($_GET['error'])) {
echo 'Error Logging In!';
}
?>
<form action="./inc/process_login.php" method="post" class="pw" name="login_form">
email: <input name="email" type="text">
pw: <input type="password" name="password">
<input type="submit" value="Submit">
</form>
</body>
</html>