I found something which I believe to be quite concerning while inspecting CodeIgniter's session handling mechanism.
$expire = $this->now - $this->sess_expiration;
$this->CI->db->where("last_activity < {$expire}");
$this->CI->db->delete($this->sess_table_name);
It appears that CI actually determines the session expiration time based on last_activity rather than a fixed, non-variable expiration field.
The problem here is that last_activity is updated to now() whenever the session is updated. So let's assume you are using CI defaults and have a session lasting 7200 seconds(2 hours) that's updated every 300 seconds(5 minutes)
The session is flagged as needing an update after 5 minutes of the last update, so if a user submits his session cookie after these 5 minutes, but before 2 hours, then CI will extend the session to last another 2 hours from this moment because last_activity will be updated to now().
This seems like a huge security risk to me, because as long as the user is active and keeps triggering the session update, its duration is extended indefinitely, effectively making the expiration setting useless. I've tested this by setting update to 10 seconds and expiration to 20. I can confirm that as long as I kept hitting refresh every 10 seconds, the session never expired!
Imagine if an attacker steals your login cookie and establishes a valid session. Even if you invalidate the login cookie, the attacker could keep his session valid indefinitely so long as he kept submitting the cookie often enough.
What do you think? Am I missing something important here? Or is this really as bad a security hole as it appears?
I think you get the point from the comments, but to simply post an answer, what you discovered is the proper function.
Sessions are ment to be 'extended' they are not created from initial fixed point in time, to + 7200 sec. Its somewhat silly if you are someone working on something and suddenly while you are active, you get logged out.
As an alternative method, if you wanted to do what you describe, you would extend the CI_Session and modify that specific method, and save it as MY_Session.
See more details here: http://codeigniter.com/user_guide/general/core_classes.html
Related
The scenario:
User logs in
Cookie is set to length of session
After 1 hour of inactivity I wish to log out the user
How I think I can solve this:
Set the session.gc_maxlifetime to 1 hour (3600)
Set the session.gc_probability to 1
Set the session.gc_divisor to 1
Therefore having a 100% certainty that garbage collection will occur on any idle session cookies after 1 hour.
My question:
All the posts and documentation I've read has never mentioned setting a gc change of 100%, therefore is it bad to do this? Is there a better way?
It's a symfony app, and long term I would like to do something like this http://symfony.com/doc/master/components/http_foundation/session_configuration.html#session-meta-data but for now I was hoping to just do something simple with session.gc_*
One post I read implies that having a 100% garbage collection chance is "cost-intensive" How do I expire a PHP session after 30 minutes? is this true? If so, how cost intensive?
Cheers!
The gc_probability and gc_divisor are there to let you define the "probability" of firing up the garbage collection (GC).
Since GC (as everything) comes with a cost, you wouldn't usually want it to run on each and every web request processed by your server - that would mean that every page opening or every AJAX request served from PHP would cause the GC to run.
So, depending on the actual server load and usage, the admin is expected to do an educated guess on how often should GC be run: once in 100, 1/10000 or 1 in million requests.
But, there's a problematic flaw in the OP's original reasoning - that garbage collection will occur on any idle session. The way I read the manual, the garbage collection will occur on ANY session, not just idle ones:
session.gc_maxlifetime integer: specifies the number of seconds after which data will be seen as 'garbage' and potentially cleaned up.
So, the session (idle or not) lifetime is decided with gc_maxlifetime, while the moment of the GC being started (as said in the docs: "potentially") is really decided with gc_probability and gc_divisor.
To resume, my late answer to the question would be - I would not under normal condition have GC running at each and every request (the 1/1 scenario you mentioned), because
that seems like a serious overkill. On some level, you would probably end up with thousands (if not worse) of IFs and only once going into its THEN
you would log out ANY user on your system after 60mins, not just the idle ones.
There are much better ways of doing this.
If this isn't for something particularly secure, you can set an expiration date/length for the session cookies on the client-side. A technically minded user could tweak the expiration in this case, so you wouldn't want to use this on a bank site.
If you need something more secure, just store an expiration time along with the other session data and check against it. If it's exceeded, destroy their session and force them to log back in.
I have 2 areas that need session management, with 2 different expirations:
Administrative Area - 20 minutes
Reservation Area - 3 minutes
Using Zend_Session, how can I manage both for the same user?
It is important to note that the Reservation Area behaves much like Ticketmaster, where maintaining reservation accuracy down to the second is crucial.
My current implementation utilizes the Zend_Session_SaveHandler_DbTable and I would like to stick with that, if possible.
I am storing the Session_ID from the main Session table on the Reservation table to indicate reservations. This works out well, because it allows me to use garbage collection to clean up abandoned sessions (I have a cron triggering garbage collection every minute).
My problem with this implementation is that I don't know how to manage the Administrative Area's session, given the time difference.
I think you need an application-level thing to manage these reservations, rather than relying on the session itself. It's hard to 'do things' when sessions expire, and I'd argue sessions are not really designed to be depended on for this sort of functionality.
I suggest you create a separate table that holds reservation tokens. These would have an unique 'token' hash as the primary key, an 'expires' date, and possibly a optional user ID. Instead of storing Session_ID on the reservation table, you store the token instead, with the same constraint in place (so deleting the token frees up the reservation). You add an additional cron job that expires tokens - this could be as simple as executing a DELETE FROM reservation_tokens WHERE expires < NOW() query. In the session you store the token, and checking the expire time on the token will tell you whether the reservation is still valid or not.
This way your reservation expiry is no longer dependent on the session expiring. It also would allow for things like a logged in user still having the same reservation if they were to login again (before the reservation expired).
As for the administrative login, an approach I've taken in the past is to simply extend the session expiry when a user authenticates. This is pretty easy to do, just after a valid login:
// assuming $result is a Zend_Auth_Result
if ($result->isValid()) {
// extend the user's session
$lifetime = 1200;
ini_set('session.cookie_lifetime', $lifetime);
Zend_Session::rememberMe($lifetime);
}
and if you've implemented the reservation token solution, having a longer session no longer causes problems with your reservations.
You can use setExpirationSeconds method in Zend_Session_Namespace.
$session->setExpirationSeconds(10);
for expiry in 10 seconds.
I am using CI's sessions in connection with a database. So all of our sessions are in this ci_sessions table on our database and it can get a lot of rows, considering that the session_id keep changing every 5 minutes.
Do we need to empty the table, say every one a month / week maybe?
While what #Marc-Audet said is true, if you take a look at the code, you can see it is a really lousy way to clean up sessions.
The constructor calls the _sess_gc function every time it is initiated. So, basically each request to your server if you have it autoloaded.
Then, it generates a random number below 100 and sees if that's below a certain value (by default it is 5). If this condition is met, then it will remove any rows on the session table with last_activity value less than current time minus your session expiration.
While this works for most cases, it is technically possible that (if the world is truly random) the random number generator does not generate a number below 5 for a long time, in which case, your sessions will not be cleaned up.
Also, if you have your session expiry time set to a long time (if you set to 0, CI will set it to 2 years) then those rows are not going to get deleted anyway. And if your site is good enough to get a decent amount of visitors, your DBA will be pointing fingers at the session table some time soon :)
It works for most cases - but I would not call it a proper solution. Their session id regeneration really should have been built to remove the records pertaining to the previous ids and the garbage collection really should not be left to a random number - in theory, it is possible that the required number is not generated as frequently as you wished.
In our case, I have removed the session garbage collection from the session library and I manually take care of it once a day (with a cron job .. and a reasonable session expiration time). This reduces the number of unnecessary hits to the DB and also does not leave a massive table in the DB. It is still a big table, but lot smaller than what it used to be.
Given the fact that the OP question doesn't have a CodeIgniter 2 tag, I'll answer how to deal with sessions cleanup when the database keeps growing for CodeIgniter 3.
Issue:
When you set (in the config.php file) sess_expiration key too high (let's say 1 year) and sess_time_to_update key low (let's say 5 min), the session table will keep growing as the users browse though your website, until sessions rows will expire and will be garbage collected (which is 1 year).
Solution:
Setting sess_regenerate_destroy key to TRUE (default set to FALSE) will delete an old session when it will regenerate itself with the new id, thus cleaning your table automatically.
No, CodeIgniter cleans up after itself...
Note
According to the CodeIgniter documentation:
The Session class has built-in garbage collection which clears out expired sessions so you do not need to write your own routine to do it.
CodeIgniter's Session Class probably checks the session table and cleans up expired entries. However, the documentation does not say when the clean up happens. Since there are no cron jobs as part of CodeIgniter, the clean up must occur when the Session class is invoked. I suppose if the site remains idle forever, the session table will never be cleared. But, this would be an unusual case.
CodeIgniter implements the SessionHandlerInterface (see the docs for the custom driver).
CodeIgniter defines a garbage collector method named gc() for each driver (database, file, redis, etc) or you can define your custom gc() for your custom driver.
The gc() method is passed to PHP with the session_set_save_handler() function, therefore the garbage collector is called internally by PHP based on session.gc_divisor, session.gc_probability settings.
For example, with the following settings:
session.gc_probability = 1
session.gc_divisor = 100
There is a 1% chance that the garbage collector process starts on each request.
So, you do not need to clean the session table if your settings are properly set.
When you call:
$this->session->sess_destroy();
It deletes the information in database by itself.
Since PHP7, the GC-based method is disabled by default, as per the documentation at https://www.php.net/manual/en/function.session-gc.php Stumbled upon this because a legacy application suddenly stopped working, reaching a system limitation since sessions are never ever cleaned up. A cronjob to clean up the sessions would be a good idea...
It is always good practice to clear the table. Otherwise, if your querying the session data for say creating reports or something, it will be slow and unreliable. Nevertheless, given the performance of mysql, yes do so.
UPDATED
So I am running into another problem that I think is pretty much the same thing. I am using silex and I want the session to be stored for 5 days. I give the have the following values:
session.cookie_lifetime: 432000
session.gc_maxlifetime: 432000
session.gc_probability: 100
session.gc_divisor: 100
So from my understanding since probability and divisor are the same number, gc should happen every page load and the session file (using php native file handler for sessions) should be kept around for 432000 (60 * 60 * 24 * 5) seconds without any activity on them. This issue is even with these value, my session seems to expire between 1-2 hours of no activity.
I even tried seeting the gc_maxlifetime to 5 and that seemed to work fine so it makes me think something else is being triggered before gc_maxlifetime is.
Are there any other values I am missing?
The server decides when to clear up stale sessions.
This happens at certain times, depending on a random number and the chance you gave it (in php.ini).
Look for session.gc_divisor.
So in your situation, your cookie with the PHPSESSID is still there, and your browser sends it as it should to the server, but the server cannot find a corresponding session.
You also need to change a couple more variables in your php.ini
.check this link
I'm caching tweets on my site (with 30 min expiration time). When the cache is empty, the first user to find out will repopulate it.
However, at that time the Twitter API may return a 200. In that case I'd like to prolong the previous data for another 30 mins. But the previous data will already be lost.
So instead I'd like to look into repopulating the cache, say, 5 minutes before expiration time so that I don't lose any date.
So how do I know the expiration time of an item when using php's memcache::get()?
Also, is there a better way of doing this?
In that case, isn't this the better logic?
If the cache is older than 30 minutes, attempt to pull from Twitter
If new data was successfully retrieved, overwrite the cache
Cache data for an indefinite amount of time (or much longer than you intend to cache anyway)
Note the last time the cache was updated (current time) in a separate key
Rinse, repeat
The point being, only replace the data with something new if you have it, don't let the old data be thrown away automatically.
don't store critical data in memcached. it guarantees nothing.
if you always need to get "latest good" cache - you need to store data at any persistent storage, such as database or flat file.
in this case if nothing found in cache - you do twitter api request. if it fails - you read data from persistent. and on another http request you will make same iteration one more time.
or you can put data from persistent into memcache with pretty shor lifetime. few minutes for example (1-5) to let twitter servers time to get healthy. and after it expired - repeat the request.
When you are putting your data into memcache - you are setting also how long the cache is valid. So theoretically you could also put the time when cache was created and/or when cache will expire. Later after fetching from cache you can always validate how much time left till cache will expire and decide what you want to do.
But letting cache to be repopulated on user visit can be still risky at some point - lets say if you would like to repopulate cache when it reaches ~5 min before expiration time - and suddenly there would be no visitors coming in last 6 minutes before cache expires - then cache will still expire and no one will cause it to be repopulated. If you want to be always sure that cache entry exists - you need to do checks periodically - for example - making a cronjob which does cache checks and fill-ups.