Page 1 of 1

Session Pool - Your frendly, secure session handler

Posted: Sat Nov 04, 2006 2:41 pm
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;
    }
	
}
?>

Posted: Sun Nov 05, 2006 9:31 pm
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.