PHP Sessions Management Class

Please read original post before continuing.


<?php 
/** *************************************************/
/** Copyright (c) 2009 Hamid Sarfraz
/**
/** @author : praisedpk@gmail.com
/** @version : 1.0
/** @web : http://phpleaks.blogspot.com/2009/05/relatively-secure-php-sessions.html
/**
/** *************************************************/

/** *************************************************/
/**
/** You can use $_SESSION just like you do under ordinary circumstances
/** Only different you have to do is use
/** $session = new session();
/** you can skip this by setting 'SESSION_AUTO_START' to true
/** to initialize new session (as an alternative of session_start() function)
/** must use
/** $session->close();
/** at the end of script to write the $_SESSION data to session file.
/**
/** *************************************************/

/** Functions ***************************************/
/** __construct() for initializing session
/** validate_id() for validating existing id
/** close() for writing the existing $_SESSION data to session file
/** destroy() for destroying session data from session file. No Change to $_SESSION
/** must be used after close() ( or do not use close() after call to destroy() )
/** or everything will be restored. (close() copies $_SESSION data to session file
/** so call to close() after a call to destroy() will make no sense.
/**
/** clear() for setting $_SESSSION to an emtry array
/** logger() for logging the errors occured in session handling. use print_r($session->error)
/** automatically echos errors except when error_reporting() is set to DISABLED
/** generate_id() for generating random session id (16 digit hexadecimal)
/** gc() collect garbage feature just like php's default
/** regenerate_id() regenerates session id, renames the session file, sends new session cookie and returns new session id.
/**
/** *************************************************/

$path = str_replace('\','/',dirname(__FILE__)).'/';
#str_replace() is to make this path linux like (if you are on windows.


/** *************************************************/
/** SESSION_SECURITY_SALT ==> Use at least 40 digit string for extreme security.*/
define('SESSION_SECURITY_SALT' , 'CAUTION: Change this text inside single quotes to something less meaningful');
/** *************************************************/

/** *************************************************/
/** SESSION_NAME ==> Script Default 'as', php's default 'PHPSESSID'. Must be alphanumeric.*/
define('SESSION_NAME' , 'as' );
/** *************************************************/

/** *************************************************/
/** SESSION_SCRIPT_PATH ==> Current File's path. Keep this file out of webroot. Dont change the below setting. Just move file and the settings will automatically adjust.*/
define('SESSION_SCRIPT_PATH' , $path );
/** *************************************************/

/** *************************************************/
/** SESSION_SAVE_PATH ==> Path to temporary directory where to keep session files. directory must be writable and must already exist.*/
define('SESSION_SAVE_PATH' , $path.'tmp/' );#don't forget the / at the end.
/** *************************************************/

/** *************************************************/
/** SESSION_LIFE ==> Minimum time (in seconds) to keep session data in session file. 1440 is php's default, so i used it as default. Change it if you want.*/
define('SESSION_LIFE' , 1440 );
/** *************************************************/

/** *************************************************/
/** SESSION_FILE_PREFIX ==> Used as prefix of session file name. For example as_134abcdef5234213.*/
define('SESSION_FILE_PREFIX' , 'as_' );
/** *************************************************/

/** *************************************************/
/** SESSION_AUTO_START ==> Starts session automatically. Just like php's autostart feature. True=autostart | false=donot autostart*/
define('SESSION_AUTO_START' , true );
/** *************************************************/

/** *************************************************/
/** SESSION_COOKIE_PATH ==> the path on your domain, for which to keep session valid and accessible. '/' means full domain.*/
define('SESSION_COOKIE_PATH' , '/' );
/** *************************************************/

/** *************************************************/
/** SESSION_COOKIE_LIFE_TIME ==> Session Cookie life time. Default is '0' (string) which means delete cookie on browser close.*/
define('SESSION_COOKIE_LIFE_TIME' , '0' );
/** *************************************************/

/** *************************************************/
/** SESSION_COOKIE_DOMAIN ==> Domain for which to make it work. default is '.example.com'. Notice the '.' in the beginning, it makes the cookie accessible along all subdomains.*/
define('SESSION_COOKIE_DOMAIN' , '.example.com' );
/** *************************************************/

/** *************************************************/
/** SESSION_COOKIE_SECURE ==> Send Session Cookie on only Secure Connection? True = yes | False = no.*/
define('SESSION_COOKIE_SECURE' , false );
/** *************************************************/

/** *************************************************/
/** SESSION_COOKIE_HTTP_ONLY ==> Disallow javascript access to cookie? True = yes | False = no.*/
define('SESSION_COOKIE_HTTP_ONLY' , true );
/** *************************************************/

/** *************************************************/
/** SESSION_GC_PROBABILITY ==> See 'session.gc_probability' in php manual. Dont change this value if you are unsure.*/
define('SESSION_GC_PROBABILITY' , 1 );
/** *************************************************/

/** *************************************************/
/** SESSION_GC_DIVISOR ==> See 'session.gc_divisor' in php manual. Dont change this value if you are unsure.*/
define('SESSION_GC_DIVISOR' , 100 );
/** *************************************************/


/** *************************************************/
/**
/**
/**
/**
/**
/**
/** DO NOT CHANGE ANYTHING BELOW
/**
/**
/**
/**
/**
/** *************************************************/

$_SESSION = array();

class session
{
var $path = SESSION_SCRIPT_PATH;
var $name = SESSION_NAME;
var $save_path = SESSION_SAVE_PATH;
var $life = SESSION_LIFE;
var $file_prefix = SESSION_FILE_PREFIX;
var $cookie_path = SESSION_COOKIE_PATH;
var $cookie_life = SESSION_COOKIE_LIFE_TIME;
var $cookie_domain = SESSION_COOKIE_DOMAIN;
var $cookie_secure = SESSION_COOKIE_SECURE;
var $http_only = SESSION_COOKIE_HTTP_ONLY;
var $gc_probability = SESSION_GC_PROBABILITY;
var $gc_divisor = SESSION_GC_DIVISOR;
var $salt = SESSION_SECURITY_SALT;

var $id;
var $errors = array();

function __construct()
{
if( isset($_COOKIE[$this->name]) )
{
$this->id = $_COOKIE[$this->name];
if( $this->validate_id($this->id) === true )
{
if( file_exists( $this->save_path . $this->file_prefix . $this->id ) )
{
if( $session_data = file_get_contents( $this->save_path . $this->file_prefix . $this->id ) )
{
$_SESSION = unserialize($session_data);
$this->id = $this->regenrate_id();
return true;
}
$this->logger('Failed to fetch session data from session file.');
}
}
}
$this->id = $this->generate_id();
setcookie( $this->name , $this->id , $this->cookie_life , $this->cookie_path , $this->cookie_domain , $this->cookie_secure , $this->http_only );
}

function validate_id( $id = '')
{
if( ctype_xdigit($id) and strlen($id) == '32' )
{
if( substr($id , 0 , 12) === substr( md5( $this->salt . ( isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '' ) . substr($id , -20 ) ) , 7 , 12 ) )
{
return true;
}
}
return false;
}

function close() #session_write_close()
{
$data = serialize($_SESSION);
if (is_writable($this->save_path ))
{
if(!$handle = fopen($this->save_path . $this->file_prefix . $this->id , 'w+'))
{
$this->logger('Failed to open session file on '. __FILE__ .' line '. ( __LINE__ -2 ).'.');
return false;
}

if (fwrite($handle, $data) === FALSE)
{
$this->logger('Failed to write session data to session file on '. __FILE__ .' line '. ( __LINE__ -2 ).'.');
return false;
}
}
else
{
$this->logger('Session file is not writable. Unable to write session data on '. __FILE__ .'.');
return false;
}

if(is_resource($handle))
{
fclose($handle);
unset($handle);
}

$gc = floor(( $this->gc_probability / $this->gc_divisor ) * rand ( 0 , $this->gc_divisor ));
if($gc)
{
$this->gc();
}

return true;
}

function destroy()#session_destroy() reset session file but no change to $_SESSION. use it for logout
{
if(is_writable($this->save_path . $this->file_prefix . $this->id ))
{
if(!$handle = fopen($this->save_path . $this->file_prefix . $this->id , 'w+'))
{
$this->logger('Failed to open session file for destroy.');
return false;
}

if (fwrite( $handle, 'a:0:{}' ) === FALSE)
{
$this->logger('Failed to destroy session data.');
return false;
}
}
else
{
$this->logger('Session file is not writable. Unable to destroy session data.');
return false;
}

if(is_resource($handle))
{
fclose($handle);
unset($handle);
}
return true;
}

function clear()
{
$_SESSION = array();
return true;
}

function logger($error)
{
$this->errors[] = $error;

if( error_reporting() !== 0 )
{
echo $error;
}
return true;
}

function generate_id()
{
$idpart[2] = substr( md5(uniqid().mt_rand('9999999' , '99999999')) , -20 ) ;
$idpart[1] = substr( md5( $this->salt . ( isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '' ) . $idpart[2]) , 7 , 12 ) ;
return $idpart[1].$idpart[2];
}

function gc($id = '')
{
#you can always call this function to force collect garbage.
if($id !== '')
{
if( !file_exists( $this->save_path . $this->file_prefix . $id ))
{
$this->logger('Provided wrong session id for deletion of session file');
return false;
}

if( !unlink( $this->save_path . $this->file_prefix . $id ) )
{
$this->logger('Failed to delete session file for id '.$id );
return false;
}

return true;
}

$dir = opendir($this->save_path);


while ($sess_file = readdir($dir))
{
if (($sess_file != ".") && ($sess_file != ".."))
{
if( fileatime($this->save_path . $sess_file ) < ( time() - $this->life ) )
{
if(!unlink( $this->save_path . $sess_file ))
{
$this->logger('Failed to delete session file '.$sess_file );
}
}

}
}
closedir($dir);
return true;
}

function regenrate_id()
{
if( !headers_sent() )
{
$newid = $this->generate_id();
if( !rename( $this->save_path . $this->file_prefix . $this->id , $this->save_path . $this->file_prefix . $newid ) )
{
$this->logger('Failed to regenrate session id.');
return false;
}
$this->id = $newid;
setcookie( $this->name , $this->id , $this->cookie_life , $this->cookie_path , $this->cookie_domain , $this->cookie_secure , $this->http_only );
}
return $this->id;
}
}


if( defined('SESSION_AUTO_START') AND SESSION_AUTO_START === true )
{
$session = new session();
}

?>

Relatively Secure PHP Sessions - An alternative to PHP Sessions

I was looking for ways to protect my website from a series of Attacks launched against Sessions, that result in loss of Personal Information of the Website users.

This article is not to explain what kind of those attacks are and the possible impacts and solutions. In this article, i will try to explain how to add a little extra security to your sessions to make your sessions much secure by implementing a custom sessions management system.

You may follow these links to read more about those attacks (just a few),
Session Fixation , Hijacking and Session Vulnerability on shared hosts.

After reading a lot about the issues and the solutions, I came to a solution that relying just on php's built-in support for sessions is not enough, and you have to apply some extra security measures to secure your user data stored in particular user's session, may be by developing a custom session management system that suits your needs.

Most of the time, security needs of all of us are the same (ie. the maximum possible security at the minimum cost), so i decided to write a piece of code that can help you manage your users' sessions with added security and the ease and flexibility that you get from php and with an advantage that you can at any time edit the configuration without the need to restart the server.

Features

So, what would you be expecting from a secure Sessions Management System? Definitely Increased Security. This system provides you a little extra:

  • Easy Customizable Configuration,
  • Most Secure Session Management specially for Shared Hosts,
  • Supports its own parameters for customization rather than relying on php's settings,
  • Built-in Support Against All Known Session Attacks (nothing to do extra. just install and use),
  • Relies on just cookies for transfer of session id to users (You cannot transfer session id through URLs),
  • A 32 digit secure id with first 3 digits for session integrity check,
  • Save users' session data anywhere you want (preferably outside the web root) with ease to change the location to save session data,
  • Error logging,
  • Automatic regeneration of session id on each request for extreme security,
  • Garbage Collection. This script automatically deletes the old and useless session files after a reasonable time. You can configure its behaviour.

How this system works?

This Sessions System is a php based script that you can easily include into your scripts to start handling sessions. You get an option to start sessions automatically (see session.autostart) or you may opt to do it yourself. Whatever way you adopt, it fetches the session id from cookie. From Which cookie to fetch the Session ID? You can set the cookie name in the Configuration file. If the cookie doesn't yet exist, it starts a new session.

Where cookie already exists, the session id is collected from the cookie and validated for integrity. Validation includes checking whether the cookie belongs to the same user that is currently sending this cookie (this check is based on HTTP_USER_AGENT). Another check is applied to ensure that some malicious user doesn't change the usercheck part of the session id, and add its own part to get access as the original user. To secure all this process, a security key is included. You can change the security key in the configuration file. Keep the security key as long as possible (possibly between 40-100 characters).

Once the session key is validated, it checks whether a session file exists for the same id! This check is included for three reasons:

  • Avoid a possibility that a malicious user guesses your security key and attempts to write a session id that passes your validation,
  • Check whether an existing session has expired (and session file deleted),
  • Handle the situation when you have changed the session file prefix in configuration.

If the outcome is success, session data is read from file and transferred to php's superglobal array $_SESSION. Now on you can access this _SESSION array anywhere from within your script.

Remember! There is no need to use session_start() to initialize session.

How to use?

Using this script is very simple, and if you are just a little familiour with OOP, you will find it very easy to edit. Here is a tutorial:

require('./directory/outside/web-root/class.sessions.php');
$session = new session();

//Now you are free to use $_SESSION;
if(isset($_SESSION['key']))
echo $_SESSION['key'].' It already Exists';
else
$_SESSION['key'] = 'Hello World!';

//force save the $_SESSION data. Use code below
$session->close();

//use
$session->destroy();
//to delete the current session data from the session file.
//Remember, if you use $session->close() after you have destroyed
//the session data, it will restore the session data because the
//destroy() doesn't unset the $_SESSION superglobal.
//it just deletes the data inside the session file.
//this function is useful when you are logging the user out, or when
//you have already closed the session file using $session->close()

//use
$session->clear();
//to unset the $_SESSION variable (but the data still exists in session file.)
//you need to use $session->close() after you have used the clear()
//function to save the changes to session (ie. remove the data from file too).

//use
$session->clear();
$session->destroy();
//to remove all traces of user's activity.

$session->errors;
//returns array of errors occured during the process. It helps
//you fixing the config problem most of the time.

//use
$session->gc($id);
//where $id may be the id of any session (this or other person's)
//it helps you force deleting the session file of a particular user

//use $session->gc();
//to delete all old unused session files. What is old? you can define it.
//Normally there is no need to use gc() to delete old files.
//This class automatically deletes old files after some time.
//Behaviour of this function is php like.

//and finally use
$session->generate_id();
//to generate a random id and return the id value.
//normally you do not need to call this function,
//session id is generated automatically.
//but this function can be called to generate a 32 digit random id.

//and use
$session->regenerate_id();
//to regenerate session id, replace existing id with this, send cookie of
//new id, rename session file with new id etc.
//CALL THIS FUNCTION BEFORE ANY ACTUAL OUTPUT STARTS
//this class automatically regenerates id on each request,
//so normally you do not need to call this function.

Requirements:

PHP's latest version that you can have.

Read Write permission and Access to file system.

Directory to save the sessions must already exist.

Security Tips:

  • Keep the session directory and this class outside the web root.
  • Do not forget to change the security code. Your system's reliability and security depends upon the security code.

This class is E_ALL AND E_NOTICE Compliant.

If you find bugs, please report here.

The class is posted here.