Out 08 2008
Increased Security With MySQL Based Web Sessions
Sessions are advantageous for activities such as performing log in operations and associating a logged-in user with consecutive requests, tracing multiple-stage online ordering processes and more.
However HTTP is a stateless protocol, which means that web servers treat every request independently of any other. This is where server-side session storage kicks in, but there’s still some security risks.
Sessions are much more secure than storing information on the client using only cookies, because the application maintains control over the contents of the session.
The only value present on the client side is a session cookie with his session ID or a URL passing the session ID, so the client can’t modify session data unless the application allows it.
But on regular sessions, the session data is stored on the server, this is a potential risk especially if you’re on a shared server hosting, someone with malicious code can access the directory where the session data is stored.
So the answer to this security risk is to use a MySQL based sessions, where session data is stored on a MySQL table instead of flat files on a web directory.
I’ve made a MySQL class to handle the MySQL based sessions, so you can use it on your site. It’s very simple to use and you does not require a lot of modifications on your existent code.
<?php // Don't forget to include your database connection // $db_link = @mysql_connect('localhost', 'db_username', 'db_password') or die('Could not connect: ' . mysql_error()); // @mysql_select_db('db_name') or die('Could not select database'); /** * MySessions * * @package MySQL Based Sessions v1.0 * @author João Romão (aka =IceBurn=) * @copyright João Romão (www.iceburn.info) * @version 2008 * @access public */ class MySessions { /** * Define if the session_id should be always regenerated * * Setting this to true will improve security, * but also increase memory usage. */ const always_regenerate = true; /** * use: mydomain.com or .mydomain.com to keep sessions between sub-domains. * @var string */ public $cookie_domain = ''; /** * usually '/' it's just fine * @var string */ public $cookie_path = '/'; /** * Sessions lifetime in seconds * @var int */ public $sess_duration = 2300; /** * Table name to store sessions. * @var string */ public $table_name = 'sessions'; /** * Should sessions be passed to URL's? 0 = no / 1 = yes (not recomended!) * @var bool */ public $use_trans_id = 0; /** * The name for the session (PHP's default is the ugly PHPSESSIONID) * @var string */ public $session_name = 'sess'; /** * MySessions::__construct() * * @access public * @return void */ public function __construct() { /** * Comment the ini_set block if you get errors * Errors happen when your host does not allow you to use ini_set function * Try use htaccess php_value/php_flag directive instead */ ini_set('session.use_cookies', 1); ini_set('session.use_trans_sid', $this->use_trans_id); ini_set('session.use_only_cookies', 0); ini_set('session.gc_maxlifetime', $this->sess_duration); ini_set('session.hash_bits_per_character', 4); ini_set('session.name', $this->session_name); session_set_save_handler(array('MySessions', 'open'), array('MySessions', 'close'), array('MySessions', 'read'), array('MySessions', 'write'), array('MySessions', 'destroy'), array('MySessions', 'gc')) or die('Could Not Set Session Handler!'); session_set_cookie_params($this->sess_duration, $this->cookie_path, $this->cookie_domain); } /** * MySessions::session_start() * * @return void */ public function session_start() { session_start(); $this->regenerate(); } /** * MySessions::regenerate() * * A session is regenerated either if specified in configurations or if somehow * an invalid session_id is passed or if implicit. * @param integer $force * @return bool */ public function regenerate($force = 0) { if (self::always_regenerate == true || strlen($this->protect(session_id())) != 32 || $force != 0) { $sid = session_id(); session_regenerate_id(true); $nid = session_id(); $qry = "UPDATE `" . $this->table_name . "` SET `id` = '" . $this->protect($nid) . "' WHERE id = '" . $this->protect($sid) . "';"; return mysql_query($qry); } } /** * MySessions::open() * * @access public * @param mixed $sess_path * @param mixed $sess_name * @return bool */ public function open($sess_path, $sess_name) { return true; } /** * MySessions::close() * * @access public * @return bool */ public function close() { return true; } /** * MySessions::read() * * @access public * @param mixed $sess_id * @return string */ public function read($sess_id) { $sql = "SELECT `data` FROM `" . $this->table_name . "` WHERE `id` = '" . $this->protect($sess_id) . "' ORDER BY `id` LIMIT 1;"; $res = mysql_query($sql); if ($res) { if (mysql_num_rows($res) == 1) { $ret = mysql_result($res, 0, 0); mysql_free_result($res); return $ret; } } return; } /** * MySessions::write() * * @access public * @param mixed $sess_id * @param mixed $data * @return bool */ public function write($sess_id, $data) { $sql = "INSERT INTO `" . $this->table_name . "` ( `id`, `access`, `data` ) VALUES ( '" . $this->protect($sess_id) . "', " . time() . ", '" . mysql_real_escape_string($data) . "' ) ON DUPLICATE KEY UPDATE `access` = " . time() . ", `data` = '" . mysql_real_escape_string($data) . "';"; return mysql_query($sql) or die('Error Writing Session Data!'); } /** * MySessions::destroy() * * Session destroy, useful for log outs * @access public * @param mixed $sess_id * @return bool */ public function destroy($sess_id) { $_SESSION = array(); if (isset($_COOKIE[$this->session_name])) { setcookie($this->session_name, '', (time() - 42000), $this->cookie_path, $this->cookie_domain); } $sql = "DELETE FROM `" . $this->table_name . "` WHERE `id` = '" . $this->protect($sess_id) . "';"; return mysql_query($sql) or die('Error Destroying Session Data!'); } /** * MySessions::gc() * * Garbage collector * @access public * @param mixed $sess_maxlifetime * @return bool */ public function gc($sess_maxlifetime) { $old = (time() - $this->sess_duration); $sql = "DELETE FROM `" . $this->table_name . "` WHERE `access` < " . intval($old) . ";"; $qry = mysql_query($sql); /** * You can comment the optimize part, and optimize your tables in an independent way. * In fact it's advised that you do it, especially if you have a hight traffic load. */ if (mysql_affected_rows() > 0) { $this->optimize(); } return true; } /** * MySessions::optimize() * * Useful function for optimizing your sessions table. * @access public * @return bool */ public function optimize() { return mysql_query('OPTIMIZE TABLE `' . $this->table_name . '`;'); } /** * MySessions::create_table() * * You can call this function at the first run to create the necessary table. * No problem to call it after the table is created, but will increase memory. * If the table does not exist, the very first call to sessions will be stored * as usual. It will always return true or die() if fail to create table. * @access public * @return bool */ public function create_table() { $sql = "CREATE TABLE IF NOT EXISTS `" . $this->table_name . "` ("; $sql .= " `id` varchar(32) NOT NULL,"; $sql .= " `access` int(10) unsigned default NULL,"; $sql .= " `data` text,"; $sql .= " PRIMARY KEY (`id`) "; $sql .= ") ENGINE=MyISAM DEFAULT CHARSET=latin1;"; $qry = mysql_query($sql) or die('An Error Occorred: ' . mysql_error()); $this->regenerate(1); return true; } /** * MySessions::protect() * * @access private * @param mixed $string * @return string */ private function protect($string) { return mysql_real_escape_string(preg_replace('/[^a-v0-9]/i', '', $string)); } /** * MySessions::debug() * * Only for debugging reasons. * @return string */ public function debug() { $ret = var_dump($_COOKIE); $ret .= var_dump($_SESSION); return $ret; } /** * MySessions::__destruct() * * @return void */ public function __destruct() { session_write_close(); } } // End of class 'MySessions' ?>
I made this class with 3 major concerns – performance, security, customizable.
All the class is documented, so I’m not going to get into much details, please ask if you have any doubts.
Usage example:
<?php /** * Usage example: */ $session = new MySessions(); // Always safe to use, but not necessary is the create_table() call. // You can use it to create the MySQL table. It must be called before // $session->session_start(); $session->create_table(); // Instead of using session_start() you must use the class built in function // Same goes for session_destroy(), use $session->destroy(session_id()) $session->session_start(); ?>
Debugging:
<?php /** * Debugging * * Comment and uncomment $_SESSIONS to see stored data */ $_SESSION['random'] = rand(1, 999); $_SESSION['another'] = rand(999, 99999); echo '<pre>', $session->debug(), '</pre >'; ?>
By this way, you will have web sessions secure, stored in a MySQL table.
UPDATE: I’ve also published this class at phpclasses.org.
You can download all the files, including documentation and usage example here.
Happy Session Securing!
Não perca os meus artigos! Subscreva a minha feed RSS.
Outubro 21st, 2008 at 09:12
hy …
i use your class but here i have a problem with this function …
<<session_name])) {
………
and i can’t distroy my sessions with “session_destroy();” i’m forced to use $sessions->destroy(); ….
in this case $this is not a objet
so please report this bug and present one isue .. thanke you .. nice and keep up
Outubro 21st, 2008 at 09:52
Hi WGR!
session_destroy() and session_start() need to be called from inside the built in class respective functions.
This is a workaround, because the class has the ability to regenerate on every request.
All other PHP’s built in functions you can use normally, like session_name(), session_id() and so on…
Here is an example:
Both must be set before any output to the browser.
Let me know if it worked for you. Thanks.
Julho 3rd, 2009 at 21:32
Waw.. I find this old post with no answer…
Yes has work.. But i musted changed some methods and variables.. If not all it’s OK .. Thanks