Welcome Guest, Not a member yet? Register   Sign In
AJAX and CI Session (v1.7) w/DB
#1

[eluser]Padraig Kennedy[/eluser]
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.
#2

[eluser]waspfactoryuk[/eluser]
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
#3

[eluser]Padraig Kennedy[/eluser]
[quote author="waspfactoryuk" date="1233205386"] ... I could use one AJAX request to do both jobs ... [/quote]

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)

Code:
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.
#4

[eluser]waspfactoryuk[/eluser]
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.
#5

[eluser]Padraig Kennedy[/eluser]
Quote: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.

Quote: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.

Quote: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.
#6

[eluser]waspfactoryuk[/eluser]
[quote author="Padraig Kennedy" date="1233255789"]
Quote: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.
[/quote]

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.

[quote author="Padraig Kennedy" date="1233255789"]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.
[/quote]

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...
Code:
// 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.
#7

[eluser]Padraig Kennedy[/eluser]
[quote author="waspfactoryuk" date="1233258476"]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.[/quote]
Here's what the documentation on the Session class says:

Quote: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.

[quote author="waspfactoryuk" date="1233258476"]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.[/quote]

Many sites implement sessions & AJAX flawlessly. This bug is specific to Code Igniter's Session.php implementation and can be fixed.
[quote author="waspfactoryuk" date="1233258476"]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.[/quote]
I don't want the session to last forever; I want it to expire after a period of inactivity.

[quote author="waspfactoryuk" date="1233258476"]
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...
Code:
// 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.[/quote]

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

Code:
// 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.
#8

[eluser]drewbee[/eluser]
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.
#9

[eluser]waspfactoryuk[/eluser]
[quote author="drewbee" date="1233279357"]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.[/quote]

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:

Quote: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 Sad

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
#10

[eluser]waspfactoryuk[/eluser]
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
Code:
$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:

Code:
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:

Code:
// 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.

Code:
// --------------------------------------------------------------------
    
    /**
     * 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(0, mt_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_sessid, TRUE));
            
            // 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.




Theme © iAndrew 2016 - Forum software by © MyBB