Session Pool - Your frendly, secure session handler

Coding Critique is the place to post source code for peer review by other members of DevNetwork. Any kind of code can be posted. Code posted does not have to be limited to PHP. All members are invited to contribute constructive criticism with the goal of improving the code. Posted code should include some background information about it and what areas you specifically would like help with.

Popular code excerpts may be moved to "Code Snippets" by the moderators.

Moderator: General Moderators

Post Reply
User avatar
DaveTheAve
Forum Contributor
Posts: 385
Joined: Tue Oct 03, 2006 2:25 pm
Location: 127.0.0.1
Contact:

Session Pool - Your frendly, secure session handler

Post by DaveTheAve »

Alright, after my failed attempt to get feedback with my PhishTank Runner, I hope I can get some (productive) feedback on this peace of code.

Session Pool:
Developed out of the need a safer way to handle client's sessions, Session Pool was developed. With the threats much like session hijacking, developers don't feel safe so why should their clients? This session handler implements much more security by locking sessions to a specific IP address (but not a IP address to a session), for added security a provided session-key must match that of the session trying to be accessed. This dual-verification and compression makes Session Pool, much more safe giving the developers and their clients a much needed feel of security.
Question: I can't seem to use/access/remove the cookie "PHPSESSID", any thoughts on how? I don't need it for this class; this classes uses a cookie with the "SessPool_" prefix.

Note: This class - as of this development release - makes use of the AdoDB abstraction layer.

Code: Select all

<?php

/**
* Manages all interactions with client's session handling
* 
* Developed out of the need a safer way to handle client's sessions, 
* Session Pool was developed. With the threats much like session hijacking, 
* developers don't feel safe so why should their clients? This session handler 
* implements much more security by locking sessions to a specific IP address (but 
* not a IP address to a session), for added security a provided session-key must 
* match that of the session trying to be accessed. This dual-verification and compression 
* makes Session Pool, much more safe giving the developers and their clients a much 
* needed feel of security. 
* 
* How To Use:
* 	Call a single instance of Session_Pool and it's automaticly implemented
* 	Session_Pool will then adapt and act exactly as the standard PHP Session Handler
* 	Except, of-course, with the added security and compression added.
* 
* @name		Session Pool
* @package	BlackLista
* @author 	David Branco <David@NeoeliteUSA.com>
* @link		http://www.NeoeliteUSA.com/
* @version 	0.8.8
* @since	Nov 4, 2006
* @license	http://creativecommons.org/licenses/by/2.5/
*/
class Session_Pool {

	public $lifetime;
	public $data;
	public $is_validated;
	public $cookie = array();
	public $debug = array();
	
	private $_needs_save;
	private $session_id;
	private $session_key;

	function __construct() {
		session_set_save_handler(
			array(&$this, 'session_start'),
			array(&$this, 'session_end'),
			array(&$this, 'read'),
			array(&$this, 'write'),
			array(&$this, 'destroy'),
			array(&$this, 'gc')
		);
   		register_shutdown_function(array(&$this, 'save'));
  		session_start();
	}
	
	public function session_start($save_path, $name) {
		global $db, $db_prefix;	
		$this->lifetime = ini_get('session.gc_maxlifetime');
		$this->cookie['name'] = 'SessPool_'.ini_get('session.name');
		$this->cookie['path'] = ini_get('session.cookie_path');
		$this->cookie['domain'] = ini_get('session.cookie_domain');
		$this->cookie['secure'] = ini_get('session.cookie_secure');
		
		if(!$db->IsConnected()) {
			$debug[] = 'Database Connection Not Found';
			return false;
		}
		
		if(!empty($_COOKIE[$this->cookie['name']])) {
			if(!preg_match( '~^(\d+)_(\w+)$~', $_COOKIE[$this->cookie['name']], $match )) {
				$debug[] = 'Invalid Cookie Format';
			}else{
				$session_id_by_cookie = $match[1];
				$session_key_by_cookie = $match[2];
				
				$row = $db->GetRow( '
					SELECT session_ID, session_key, session_data, session_id
					FROM '.$db_prefix.'sessions
					WHERE session_id  = '.$this->db_escap($session_id_by_cookie).'
					AND session_key = "'.$this->db_escape($session_key_by_cookie).'"
					AND session_ipaddress = "'.$this->db_escape($_SERVER["REMOTE_ADDR"]).'"
					AND session_lastseen > '.(time()-$this->lifetime));
				
				if(empty($row )){
					$debug[] = 'Session ID/key combination is invalid!';
				}else{
					$this->session_id = $row['session_id'];
					$this->session_key = $row['session_key'];
					$this->is_validated = true;
					
					if(!empty($row['session_data']))
						$this->data = unserialize(gzuncompress($row['session_data']));
				}
			}
		}
		
		if($this->session_id)  {
			$this->_needs_save = true;
		}else{
			$this->session_key = md5(mt_rand(0,1024));
            
			$db->Execute( '
				INSERT INTO '.$db_prefix.'sessions (session_key, session_lastseen, session_ipaddress, session_expires)
				VALUES (
					"'.$this->db_escape($this->session_key).'",
					'.time().',
					"'.$this->db_escape($_SERVER["REMOTE_ADDR"]).'",
					'.(time()+$this->lifetime).'
				)' );

			$this->session_id = $db->lastInsID;
			
		}
		setcookie($this->cookie['name'], $this->session_id.'_'.$this->session_key, (time()+$this->lifetime), $this->cookie['path'], $this->cookie['domain'], $this->cookie['secure']);
		return true;
	}
	
	public function session_end() {
		$this->gc($this->lifetime);
		return $this->save();
	}    
	
	public function read($session_id) {
		return $this->data;
	}
	
	public function write($session_id, $data) {
		$this->data = $data;
		$this->_needs_save = true;
		return ($this->data == $data); 
	}
	
	public function destroy($session_id) {
		global $db, $db_prefix;
		$db->Execute('DELETE FROM '.$db_prefix.'sessions WHERE session_id = '.$this->db_escape($this->session_id));
		($db->affected_rows() > 0) ? $result = 1 : $result = 0;
		if($result)
			return true;
		return false; 
	}
	
	public function flush($session_id) {
		global $db, $db_prefix;
		echo '<pre>';
		print_r($this->session_data);
		echo '</pre>';
		
		$db->Execute('DELETE FROM '.$db_prefix.'sessions WHERE session_id = '.$this->db_escape($this->session_id));
		($db->affected_rows() > 0) ? $result = 1 : $result = 0;
		if($result)
			return true;
		return false; 
	}
	
	public function gc($maxlifetime) {
		global $db, $db_prefix;
		$db->Execute('DELETE FROM '.$db_prefix.'sessions WHERE session_expires < '.time());
		($db->affected_rows() > 0) ? $result = 1 : $result = 0;
		return $result; 
	}
	
	public function db_escape($string) {
		if (get_magic_quotes_gpc())
			$string = stripslashes($string);
		htmlentities($string, ENT_QUOTES);
		$string = mysql_real_escape_string($string);
		return $string;
	}
	
	public function save() {
		global $db, $db_prefix;

		if(!$this->_needs_save)
			return true;

		$sess_data = empty($this->data) ? NULL : gzcompress(serialize($this->data), 9);
		$db->Execute( '
			UPDATE '.$db_prefix.'sessions SET
				session_data = "'.$this->db_escape($sess_data).'",
				session_ipaddress = "'.$this->db_escape($_SERVER["REMOTE_ADDR"]).'",
				session_key = "'.$this->db_escape($this->session_key).'",
				session_lastseen = '.time().',
				session_expires = '.(time()+$this->lifetime).'
			WHERE session_id = '.$this->session_id);

		$this->_needs_save = false;
		($db->affected_rows() > 0) ? $result = 1 : $result = 0;
		return $result;
    }
	
}
?>
User avatar
John Cartwright
Site Admin
Posts: 11470
Joined: Tue Dec 23, 2003 2:10 am
Location: Toronto
Contact:

Post by John Cartwright »

I defiantly dislike the use of globals, instead look into passing the database object to the class.

I would be interested to see some benchmarks as well.. after a quick glance everything else is looking good.
Post Reply