EllisLab text mark
Advanced Search
1 of 3
1
   
AJAX and CI Session (v1.7) w/DB
Posted: 15 January 2009 11:48 AM
Avatar
Joined: 2008-02-18
19 posts

When used together, AJAX requests and the Code Igniter session library (with database storage) do not get along very well.

To frustrate man in the middle attacks, CI regenerates the session every $config[“sess_time_to_update”] seconds (default is 5 minutes, I believe).  This works by calling the sess_update() method in Session.php every time the session class is initialised. 

If it’s been more than sess_time_to_update seconds:

1. A new session_id is generated
2. The database is updated with this new session_id
3. The new session_id is sent back to the browser in a cookie.

This is problematic when multiple parallel requests are made.  Parallel requests are common when using AJAX.  Consider the following scenario:  2 parallel requests are made, just as we exceed the sess_time_to_update threshold.  The first one triggers the regeneration of the session_id and returns normally.  The second one, however, is still using the old and recently invalidated session_id.  A new session is created for this request, which contains none of the userdata and is therefore not marked as logged in.  The result is that the user gets logged out sooner than he/she should.

As a temporary work-around, I am planning to simply disable the regeneration of sessions by commenting out the sess_update() at about line 105 of Session.php

I realise that this decreases security somewhat, but our service is provided over SSL, so it is unlikely that a session key could be grabbed mid transmission and since only server generated session ids are accepted, Session Fixation should not be possible.

Any other ideas on how to deal with this?  Is there anything else I should worry about after turning off the session update?

Thanks,

Pádraig.

 
Posted: 28 January 2009 07:03 PM   [ # 1 ]   [ Rating: 0 ]
Joined: 2009-01-03
9 posts

I’m seeing the exact same problem where two AJAX requests are fired off pretty much at the same time.  One works, the other doesn’t.  I try again and it’s fine for 5 minutes until it bombs out again.

Now, I could use one AJAX request to do both jobs and handle the updates with some additional JavaScript, or just delay one of the requests by a couple of seconds and either of these may solve the problem from the client end but if anyone has any other workarounds, ideas would be gratefully received smile

Alan

 
Posted: 29 January 2009 06:46 AM   [ # 2 ]   [ Rating: 0 ]
Avatar
Joined: 2008-02-18
19 posts
waspfactoryuk - 29 January 2009 12:03 AM

... I could use one AJAX request to do both jobs ...

That would work, but it sounds like a big job just to work around a CI bug.  You could comment out sess_update() in Session.php like this: (starts around line 99)

if ( ! $this->sess_read())
{
    $this
->sess_create();
}
else
{    
    
/*       
    This problem was posted on the CI forum, here:
    http://ellislab.com/forums/viewthread/102456/
        
    $this->sess_update();
    
    */

It makes sessions marginally less secure;  Since the session id will last longer, someone trying to steal the session gets more time to work.

Alternatively, if you can turn database storage of sessions off, it shouldn’t happen anymore.

 
Posted: 29 January 2009 08:20 AM   [ # 3 ]   [ Rating: 0 ]
Joined: 2009-01-03
9 posts

It’s not really a CI bug, more like an additional security feature that just isn’t compatible with the multiple AJAX requests. If you’re not using AJAX then race hazards are not really an issue.

Rather than update the source, I’ve just set this in my system/application/config/config.php file:
$config[‘sess_time_to_update’] = 7200;

This just makes sure the update happens after 2 hours. As I have sessions set to expire after an hour, this effectively turns off the feature.

I’d prefer to keep the database session storage as it’s more secure than using cookie storage and doesn’t need any addition to the framework.  Yes it would be possible to use the native PHP sessions mod but that would still give rise to the same issue I think whereby the session is updated between requests.

 
Posted: 29 January 2009 09:03 AM   [ # 4 ]   [ Rating: 0 ]
Avatar
Joined: 2008-02-18
19 posts

It’s not really a CI bug, more like an additional security feature that just isn’t compatible with the multiple AJAX requests. If you’re not using AJAX then race hazards are not really an issue.

I would argue that the unexpected loss of session user data during certain requests is a bug.  It may not be a critical one and it may not effect everyone using CI, but it is a bug.

Rather than update the source, I’ve just set this in my system/application/config/config.php file:
$config[‘sess_time_to_update’] = 7200;

This just makes sure the update happens after 2 hours. As I have sessions set to expire after an hour, this effectively turns off the feature.

This is definitely a good work around, if it suits your application.  It’s always better not to modify framework code!

For some web applications, however, having sessions expire after an hour can be very irritating since it’s an hour after logging in rather than an hour since last activity.  Our users would on occasion spend 5 minutes filling out a form (while on the phone to a client of their own, who won’t want to repeat the information!), only to lose it when they get kicked back to the log in screen because their session timed out. 

I’d prefer to keep the database session storage as it’s more secure than using cookie storage and doesn’t need any addition to the framework.  Yes it would be possible to use the native PHP sessions mod but that would still give rise to the same issue I think whereby the session is updated between requests.

I agree about database storage, and I don’t see this as a reason to switch session libraries.

 
Posted: 29 January 2009 09:47 AM   [ # 5 ]   [ Rating: 0 ]
Joined: 2009-01-03
9 posts
Padraig Kennedy - 29 January 2009 02:03 PM

It’s not really a CI bug, more like an additional security feature that just isn’t compatible with the multiple AJAX requests. If you’re not using AJAX then race hazards are not really an issue.

I would argue that the unexpected loss of session user data during certain requests is a bug.  It may not be a critical one and it may not effect everyone using CI, but it is a bug.

The thing is, it’s not unexpected - it’s even documented and operating exactly as per spec.  Therefore it’s a feature and not a bug.  Unfortunately for us, the feature just isn’t suitable for the situation we’re creating by using AJAX where requests can hit almost simultaneously.  Regardless of whether the server is running CI or not, updating the session with a new ID will mess up things for AJAX.  The approach just isn’t suitable where AJAX is concerned. 

Padraig Kennedy - 29 January 2009 02:03 PM

For some web applications, however, having sessions expire after an hour can be very irritating since it’s an hour after logging in rather than an hour since last activity.  Our users would on occasion spend 5 minutes filling out a form (while on the phone to a client of their own, who won’t want to repeat the information!), only to lose it when they get kicked back to the log in screen because their session timed out.

You can always set a long expiry time in the config and then set ‘sess_time_to_update’ slightly bigger if you want to avoid getting kicked off but then you’re relying on the user to log off to destroy the session. 

However, looking at the Session.php file, it seems like the session expiry is based on last activity not creation time (although the sess_time_to_update will be based on creation time of course otherwise it wouldn’t get updated as long as requests arrive).

Line 171…

// Is the session current?
if (($session['last_activity'$this->sess_expiration) < $this->now)
{
    $this
->sess_destroy();
    return 
FALSE;

I’m not sure why it’s not working for you.  Just set sess_time_to_update to something like 2 years and job done.

 
Posted: 29 January 2009 01:41 PM   [ # 6 ]   [ Rating: 0 ]
Avatar
Joined: 2008-02-18
19 posts
waspfactoryuk - 29 January 2009 02:47 PM

The thing is, it’s not unexpected - it’s even documented and operating exactly as per spec.  Therefore it’s a feature and not a bug.

Here’s what the documentation on the Session class says:

The Session class permits you maintain a user’s “state” and track their activity while they browse your site.

When two or more parallel requests are made at the same time, the session class will intermittently fail to maintain a user’s state.  I call that a bug.  At best, it’s an undocumented limitation — it is not a feature.

waspfactoryuk - 29 January 2009 02:47 PM

Regardless of whether the server is running CI or not, updating the session with a new ID will mess up things for AJAX.  The approach just isn’t suitable where AJAX is concerned.

Many sites implement sessions & AJAX flawlessly.  This bug is specific to Code Igniter’s Session.php implementation and can be fixed.

waspfactoryuk - 29 January 2009 02:47 PM

You can always set a long expiry time in the config and then set ‘sess_time_to_update’ slightly bigger if you want to avoid getting kicked off but then you’re relying on the user to log off to destroy the session.

I don’t want the session to last forever; I want it to expire after a period of inactivity.

waspfactoryuk - 29 January 2009 02:47 PM

 
However, looking at the Session.php file, it seems like the session expiry is based on last activity not creation time (although the sess_time_to_update will be based on creation time of course otherwise it wouldn’t get updated as long as requests arrive).

Line 171…

// Is the session current?
if (($session['last_activity'$this->sess_expiration) < $this->now)
{
    $this
->sess_destroy();
    return 
FALSE;

I’m not sure why it’s not working for you.  Just set sess_time_to_update to something like 2 years and job done.

This approach doesn’t work in practice.  When the session is created, “sess_create()” calls “_set_cookie()” which writes a cookie as follows:

// From line 675, Sessions.php
// Set the cookie
setcookie(
            
$this->sess_cookie_name,
            
$cookie_data,
            
$this->sess_expiration time(),
            
$this->cookie_path,
            
$this->cookie_domain,
            
0
        
); 

The web browser will expire this cookie after “$this->sess_expiration + time()”.  This is usually updated by a call from “sess_update()”, so typically it is not a problem.  As soon as you set sess_time_to_update > sess_expiration, however, “sess_update()” will never run and the cookie will never get updated.  So in this context, sess_expiration is in fact measured from when the session was created.

 
Posted: 29 January 2009 03:35 PM   [ # 7 ]   [ Rating: 0 ]
Avatar
Joined: 2008-07-28
511 posts

Unfortunately for me, this was not an option. To me it was clearly a bug and I have made my own session class to take care of this.

My session class currently only makes two total queries, on each and every page load. sess_read for the initial session data, and during the __destruct() will update any changed data.

I also changed the sess_time_to_update to work within this very last update, however I will still see the same results if the AJAX call is the one to hit the expiry time.

I will probably have to come up with a way to notify ajax calls to force the session to not regenerate the id, and leave it strictly for main browser calls unfortunately.

 Signature 

~ 4 All the Right Reasons ~

 
Posted: 29 January 2009 03:50 PM   [ # 8 ]   [ Rating: 0 ]
Joined: 2009-01-03
9 posts
drewbee - 29 January 2009 08:35 PM

I will probably have to come up with a way to notify ajax calls to force the session to not regenerate the id, and leave it strictly for main browser calls unfortunately.

I’m quickly coming to that conclusion too…

Having worked through Sessions.php and how it works in relation to AJAX, I think I now fully appreciate the problem. The CodeIgniter documentation says:

When a page is loaded, the session class will check to see if valid session data exists in the user’s session cookie. If sessions data does not exist (or if it has expired) a new session will be created and saved in the cookie. If a session does exist, its information will be updated and the cookie will be updated. With each update, the session_id will be regenerated.

So, if the sess_time_to_update time has passed, or if session data is changed, the session_id will automatically be changed.  CodeIgniter relies on sess_update() to update the client’s cookie expiry so if sess_update is not called then the session cookie will eventually expire and the session will die.

AJAX requires that the session_id remains constant and does not change.  This is not achievable using the CodeIgniter sessions implementation as there is currently no way to turn off the changing of the session ID.

So, the bad news is that our workarounds here don’t work as they don’t prevent the change of session ID in all circumstances and still allow the cookie expiry to be reset :(

What I think anyone using AJAX in an environment using sessions for logon security needs is a modification that allows us to switch off the changing of the session ID so it remains constant as per native sessions - or at least allows us to trigger it manually from within a non-AJAX controller.  An extra config parameter would be nice to turn it on and off.

Time to start hacking it about smile

 
Posted: 29 January 2009 05:10 PM   [ # 9 ]   [ Rating: 0 ]
Joined: 2009-01-03
9 posts

Okay, I’ve tweaked it about a bit to make changing session ID optional…

First of all, I added a new config variable in config.php

$config['sess_retain_session_id'TRUE

Then I copied Session.php to my system/application/libraries folder and made some changes, first adding the new var:

class CI_Session {

    
var $sess_retain_session_id        FALSE;    //New option - keep same session id on update
    
var $sess_encrypt_cookie        FALSE

Next, change the constructor to read the new config var:

// Set all the session preferences, which can either be set 
        // manually via the $params array above or via the config file
        //foreach (array('sess_encrypt_cookie', 'sess_use_database', 'sess_table_name', 'sess_expiration', 'sess_match_ip', 'sess_match_useragent', 'sess_cookie_name', 'cookie_path', 'cookie_domain', 'sess_time_to_update', 'time_reference', 'cookie_prefix', 'encryption_key') as $key)
        
foreach (array('sess_retain_session_id','sess_encrypt_cookie''sess_use_database''sess_table_name''sess_expiration''sess_match_ip''sess_match_useragent''sess_cookie_name''cookie_path''cookie_domain''sess_time_to_update''time_reference''cookie_prefix''encryption_key') as $key)
        
{
            $this
->$key = (isset($params[$key])) ? $params[$key] $this->CI->config->item($key);
        

Now there is a config parameter to switch on/off this behaviour, a quick tweak to sess_update is needed to act on the config var.

// --------------------------------------------------------------------
    
    /**
     * Update an existing session
     *
     * @access    public
     * @return    void
     */
    
function sess_update()
    
{
        
// We only update the session every five minutes by default
        
if (($this->userdata['last_activity'$this->sess_time_to_update) >= $this->now)
        
{
            
return;
        
}
            
        
// Save the old session id so we know which record to 
        // update in the database if we need it
        
$old_sessid $this->userdata['session_id'];
            
        
// Update the session id  (default behaviour)
        
if(!$this->sess_retain_session_id)
        
{

            $new_sessid 
'';
            while (
strlen($new_sessid) < 32)
            
{
                $new_sessid 
.= mt_rand(0mt_getrandmax());
            
}
            
            
// To make the session ID even more secure we'll combine it with the user's IP
            
$new_sessid .= $this->CI->input->ip_address();
            
            
// Turn it into a hash
            
$new_sessid md5(uniqid($new_sessidTRUE));
            
            
// Update the session data in the session data array
            
$this->userdata['session_id'$new_sessid;
        
}
        
else
        
{
            $new_sessid 
$old_sessid;
        
}
        
        
// Update the last activity data in the session data array
        
$this->userdata['last_activity'$this->now;
        
        
// _set_cookie() will handle this for us if we aren't using database sessions
        // by pushing all userdata to the cookie.
        
$cookie_data NULL;
        
        
// Update the session ID and last_activity field in the DB if needed
        
if ($this->sess_use_database === TRUE)
        
{
            
// set cookie explicitly to only have our session data
            
$cookie_data = array();
            foreach (array(
'session_id','ip_address','user_agent','last_activity') as $val)
            
{
                $cookie_data[$val] 
$this->userdata[$val];
            
}
            
            $this
->CI->db->query($this->CI->db->update_string($this->sess_table_name, array('last_activity' => $this->now'session_id' => $new_sessid), array('session_id' => $old_sessid)));
        
}
        
        
// Write the cookie
        
$this->_set_cookie($cookie_data);
    
}
    
    
// -------------------------------------------------------------------- 

It could probably do with an additional method to manually force a change of session ID for use in non-AJAX files - which would be more secure.  However, my AJAX is now working with no transient failures.  As the sess_update runs every 5 minutes again, the user doesn’t get logged out due to the cookie expiring regardless of activity.

 
Posted: 29 January 2009 07:07 PM   [ # 10 ]   [ Rating: 0 ]
Avatar
Joined: 2008-02-18
19 posts

Here is the bug report that I filed — keep an eye on this, I guess, for a fix from EllisLab.

One approach to fixing this would be to insert a new record, when regenerating the session ID, instead of updating the old record.  As soon as a subsequent request was received using the new session ID, the old one could be removed.  This would allow the session update mechanism to continue to work as intended (regenerating the session ID often) but it would also accommodate parallel requests.

 
Posted: 29 January 2009 07:19 PM   [ # 11 ]   [ Rating: 0 ]
Joined: 2009-01-03
9 posts
Padraig Kennedy - 30 January 2009 12:07 AM

As soon as a subsequent request was received using the new session ID, the old one could be removed.

You’d need to track the old session id in the new record so you knew what you were meant to be deleting but duplicating the session on the database and cleaning up later could be a nice approach.

 
Posted: 29 January 2009 07:45 PM   [ # 12 ]   [ Rating: 0 ]
Avatar
Joined: 2008-02-18
19 posts
waspfactoryuk - 30 January 2009 12:19 AM
Padraig Kennedy - 30 January 2009 12:07 AM

As soon as a subsequent request was received using the new session ID, the old one could be removed.

You’d need to track the old session id in the new record so you knew what you were meant to be deleting but duplicating the session on the database and cleaning up later could be a nice approach.

Yeah, we’d need a new column for that I think.  We’d also need to think carefully about any requests that would change session variables in between that switch over.

The process:

1) A request arrives as the sess_time_to_update is exceeded.
2) A new session ID, “N” is generated, stored to the database and returned to the browser in a cookie.  The old record, “O” is tagged for retirement.
3) Any subsequent requests from the “O” session would be processed (however an “N” cookie would be returned each time).
4) As soon as a request from the “N” session arrived, “N” would switch to being the primary session ID:  The user state data from “O” would be transferred over to “N”, and “O” would be removed.

 
Posted: 08 April 2009 07:35 AM   [ # 13 ]   [ Rating: 0 ]
Avatar
Joined: 2008-02-18
19 posts

Any new thoughts on this?  It’s still broken in 1.7.1

PK

 
Posted: 08 April 2009 08:11 AM   [ # 14 ]   [ Rating: 0 ]
Avatar
Joined: 2008-01-03
728 posts

I haven’t read all the posts as far as I get it, the problem is that the session class updates the session id even though the request is “only” an AJAX request.

A possible solution I just thought of (untested!!) is to add a check to the sess_update() method and only update the session when the request is not done using AJAX using a function similar to this one: http://ellislab.com/forums/viewthread/108967/#549625

 Signature 

Blog - Twitter

DBlog

MeNeedz: Auth - Cloud - Password - Search - Shoutbox - Akismet -
Twitter - Visitor tracking

 
Posted: 08 April 2009 10:23 AM   [ # 15 ]   [ Rating: 0 ]
Avatar
Joined: 2008-07-28
511 posts

Yeah. I have completely overloaded the stock CI session class, so this is no longer an issue for me.

 Signature 

~ 4 All the Right Reasons ~

 
1 of 3
1