Page 1 of 1

Custom session handler?

Posted: Tue Oct 10, 2006 11:55 am
by RecoilUK
Hi guys

I am trying to write a custom session handler, using the tutorial on ZEND as a basis.

http://www.zend.com/zend/spotlight/code ... sl&id=2752

But I'm trying to code something that will prevent session fixation attacks, and i,m having a spot of bother, seem to be caught in a chicken and egg situation.

Basically, what I want to do, is check if a session id is already in the session database and check the characters in the session_id, if its not, or, the wrong characters are present, dont use it and create a new session id with session_regenerate_id. Now, I cant use session_regenerate_id, because it doesnt work in the custom session handler, because either the session isnt yet open, or, the headers have already been sent.

It does however work fine outside the class, if session_start() has already been called, as normal.

How can I get around this?

Any idea's?

Thanks in advance

Posted: Tue Oct 10, 2006 12:36 pm
by Oren
There shouldn't be a problem. Show us your code.

Posted: Tue Oct 10, 2006 2:12 pm
by RecoilUK
Hi Oren

Thx for the reply, here's the code as it stands, just so I can get the regeneration working.

Code: Select all

<?php

/* Create new object of class */
$sess_class = new session();

/* Change the save_handler to use the class functions */
session_set_save_handler (array(&$sess_class, '_open'),
                          array(&$sess_class, '_close'),
                          array(&$sess_class, '_read'),
                          array(&$sess_class, '_write'),
                          array(&$sess_class, '_destroy'),
                          array(&$sess_class, '_gc'));

session_start();

/* Defines the class */
class session
{
    /* Define the mysql table you wish to use with this class, this table MUST exist. */
    var $sess_table = "sessions";

    var $sess_host = "localhost";
    var $sess_user = "username";
    var $sess_pass = "password";
    var $sess_db = "database";
    
    var $sess_link;
    
    
    
    /* Open session */
    function _open($path, $name) {    
        $this->sess_link = @mysqli_connect ($this->sess_host, $this->sess_user, $this->sess_pass, $this->db);
        if (!$this->sess_link) {
          exit ("Error - sessions seem to be malfunctioning");
        } 
        $sessionold = session_id();
        echo $sessionold;
        session_regenerate_id();
        $sessionnew = session_id();
        echo "<br>" . $sessionnew;
            
    }

    /* Close session */
    function _close() {
        /* This is used for a manual call of the session gc function */
        $this->_gc(0);
        return TRUE;
    }

    /* Read session data from database */
    function _read($sess_id) {
        $session_sql = "SELECT * FROM " . $this->sess_table . " WHERE ses_id = '$sess_id'";
        $session_res = @mysqli_query ($this->sess_link, $session_sql);
        if (!$session_res) {
            return '';
        }
        $session_num = @mysqli_num_rows ($session_res);
        if ($session_num > 0) {
            $session_row = @mysqli_fetch_assoc ($session_res);
            $ses_data = $session_row["ses_value"];
            return $ses_data;
        } else {
            return '';
        }
    }

    /* Write new data to database */
    function _write($sess_id, $sess_data) {
        $session_sql = "UPDATE " . $this->sess_table . " SET ses_time='" . time() . "', ses_value='$sess_data' WHERE ses_id='$sess_id'";
        $session_res = @mysqli_query ($this->sess_link,$session_sql);
        if (!$session_res) {
            return FALSE;
        }
        if (mysqli_affected_rows ($this->sess_link)) {
            return TRUE;
        }
        $session_sql = "INSERT INTO " . $this->sess_table . " (ses_id, ses_time, ses_start, ses_value)" . " VALUES ('$sess_id', '" . time() . "', '" . time() . "', '$sess_data')";
        $session_res = @mysqli_query ($this->sess_link,$session_sql);
        if (!$session_res) {
            return FALSE;
        }         else {
            return TRUE;
        }
    }

    /* Destroy session record in database */
    function _destroy($ses_id) {
        $session_sql = "DELETE FROM " . $this->sess_table . " WHERE ses_id = '$ses_id'";
        $session_res = @mysqli_query ($this->sess_link,$session_sql);
        if (!$session_res) {
            return FALSE;
        }         else {
            return TRUE;
        }
    }

    /* Garbage collection, deletes old sessions */
    function _gc($life) {
        $ses_life = strtotime("-5 minutes");
        $session_sql = "DELETE FROM " . $this->sess_table . " WHERE ses_time < $ses_life";
        $session_res = @mysqli_query ($this->sess_link,$session_sql);
        if (!$session_res) {
            return FALSE;
        }         else {
            return TRUE;
        }
    }    
}

?>

Posted: Tue Oct 10, 2006 3:42 pm
by Oren
Why don't you put session_set_save_handler() and session_start() within a constructor?
And please, no offense, but it's pretty hard to understand your posts - to me at least, I don't know about the native speakers here.
RecoilUK wrote:Thx for the reply, here's the code as it stands, just so I can get the regeneration working.
Does that mean that with this code it works, or that with this code it doesn't work?
If it does work, what's the problem?
Although I'm not sure what the problem is, try to put session_set_save_handler() and session_start() within a constructor, it might solve the problem :P

Posted: Tue Oct 10, 2006 4:31 pm
by RecoilUK
Hi

Thanks again for the reply.

I,m going to try what you suggested as soon as I finished writing this.

As far as not understanding my posts, well, I cant really help that. I have to assume that a person understands if I,m asking for help, you can pretty much guarantee that its not working.

Thx

Posted: Wed Oct 11, 2006 1:03 am
by Oren
RecoilUK wrote:As far as not understanding my posts, well, I cant really help that. I have to assume that a person understands if I,m asking for help, you can pretty much guarantee that its not working.
Yes, but maybe you want to do it in a different way and currently only this way (which you don't like) works for you :P

Posted: Wed Oct 11, 2006 2:19 am
by RobertGonzalez
When you say the headers have already been sent, are you talking about content? If not, regenerate the id. Unless something (like a call to echo) sends something to the browser, you should be golden.

Also, I would look at Oren's suggestion about encapsulating all session related items into the class.

Posted: Wed Oct 11, 2006 8:13 am
by RecoilUK
Hi guys

Thanks for the help.

Yep, encapsulating the session_set_save_handler() and session_start() did the trick, everything is working as expected now.

Cheers

Posted: Wed Oct 11, 2006 8:53 am
by Oren
Don't put session_regenerate_id() in the constructor though. It won't break the code, but there is no point to do that.
I use session_regenerate_id() only when there isn't an active session associated with the session identifier that the user is presenting in order to prevent Session Fixation as suggested here.
So right after calling sesion_start() inside my constructor, I have this code:

Code: Select all

/* Prevent Session Fixation */

if (!isset($_SESSION['_initiated']))
{
	session_regenerate_id();

	$_SESSION['_initiated'] = true;
}
I'm glad we could help, cheers :P

Posted: Wed Oct 11, 2006 12:11 pm
by RecoilUK
Hi

If your interested here,s my constructor as it stands at the moment.

Code: Select all

function __construct() {
    
        session_set_save_handler(array(&$this,'_open'),array(&$this,'_close'),array(&$this,'_read'),array(&$this,'_write'),array(&$this,'_destroy'),array(&$this,'_gc'));
        $pattern = '/[a-z0-9]+$/';
        if (isset($_COOKIE['PHPSESSID'])) {
            if (!preg_match($pattern, $_COOKIE['PHPSESSID'])) {
                unset($_COOKIE[session_name()]);
            }
        }
        session_start();
        echo session_id();
        if (!isset($_SESSION['_initiated'])){
            session_regenerate_id();
            $_SESSION['_initiated'] = true;
        }        
    }
Basically I added a check to make sure only allowed characters are in the session_id.

L8rs