Tuesday 14 June 2011

How PHP Sessions Work

Recently I was trying to debug some code that was monitoring the number of current sessions for various user groups. Along the way I found out a bit more about how PHP sessions work, so I thought I'd document it here and maybe someone else will find it useful.

Starting a Session
I recommend the following to start a session in PHP:

if (!session_id()) {
    session_start();
}

This works in all situations (whether or not session.auto_start is enabled) and won't produce a E_NOTICE if the session has been previously started. It also looks tidy and doesn't require the error suppressor '@' which I prefer to avoid.

How Sessions are Stored on the web server
By default the sessions are stored in files in a temporary file location, on my Ubuntu desktop this is /var/lib/php5. You can get or set this value with the session_save_path function or the session.save_path runtime setting. If you don't want to use files to store session information you can also define a custom storage handler, for example a database, I'm just using the PHP default here.

Each session has a unique id associated with it, you can retrieve this with the session_id function, this id is used as the filename prefixed with 'sess_', for example sess_846a738ce3a570964c4d70fdc198bc6d. The session variables are stored within the file, for example the code:

$_SESSION['user'] = 'ewen';
$_SESSION['logged_in'] = true;
$_SESSION['id'] = 159753;

Produces file contents of:

user|s:4:"ewen";logged_in|b:1;id|i:159753;

For more information on how PHP variables are serialised as strings see the serialize function.

How Sessions are linked to a Request
For the web server to know which requests are linked to which session the client must store the session id. The best and most common way to do this is to store it in a cookie, by default it will be stored with the name PHPSESSID, you can modify this with the runtime setting session.name or the function session_name. By default the session cookie will expire once the browser is closed, of course this can also be modified with a runtime setting - session.cookie_lifetime.

PHP also has a fall back mechanism to pass the session id as a URL parameter if cookies are unavailable, since PHP v5.3 this is disabled by default as it makes it very easy for your session id to be captured and your session hijacked. I recommend that you make sure the runtime setting session.use_only_cookies is set to 1.

Some Tips on Saving to the Session
  • Don't save resources to the session, for example a database connection or file handle.
  • Don't save large amounts of data to the session, the overhead of a session is fairly high when using files as the storage handler. For example, instead of storing all of the current user's data in the session store just their user ID and use that to retrieve the other needed user data from a database. An exception to this rule is for multi-page forms where it makes sense to store in the session so you can process it into the database in a single atomic transaction.
  • If storing objects to the session, make sure that the class definition is available when retrieving it from the session otherwise there may be errors when unserializing the object.

Hijacking Sessions
To hijack a session all you need is the session id. Here are some ways that the session id might get captured:
  • Monitor the traffic (if it's unencrypted), see the screenshot below.
  • If you have shell access to the web server you can list files in the sessions directory (even if you can't read the file the session id is stored in the filename).
  • Get the session id from the cookie stored by the users browser.
  • If the session id is being passed in the URL, clicking on an external site link could make the id available to the other site through the HTTP header field 'referrer'.
Wireshark traffic capture showing the Session ID

Once you know a session id, you can create a cookie using a browser extension (I use Edit This Cookie for Chrome), then simply visit the site and you will now have hijacked the previous users session.

Editing the PHPSESSID cookie


Session Expiry and Garbage Collection
Sessions expire in PHP when the client's browser deletes their cookie, by default this is once they close their browser. However, the users session data on the server gets garbage collected on the server after a certain amount of time. This is done in two steps:

1. Session Data is flagged as 'garbage'. After session.gc_maxlifetime seconds, the session data is seen as 'garbage' and is flagged to be removed next time the garbage collector is run. By default this occurs 1440 seconds or 24 minutes after the session files last access time. Importantly, even though the data is 'garbage' doesn't necessarily mean it will be deleted any time soon.

2. The Garbage Collector is run and deletes 'garbage' session data. Every time a session is initialised there is a chance the garbage collector will run. By default the probability it will run is 1%, the probability is calculated using two runtime configuration values, session.gc_probability and session.gc_divisor. The probability (default value 1) is divided by the divisor (default value 100) to get the percentage change e.g. 1 / 100 = 0.01 = 1%. Therefore even though a users session has been idle for longer than the max lifetime value their session data may live much longer, so if you're implementing a site where the user's session should expire after a precise length of time you'll need to do more than just set the max lifetime.


Destroying Sessions
To destroy a users session data you can use the function session_destroy. Note that this doesn't instantly unset all of the session data for the rest of the request so you might want to redirect the user after running session_destroy or empty all of the session variables.

session_destroy(); // destroy the file containing the users session data
$_SESSION = array(); // optionally clear the session variables for the rest of the script execution

If you want to totally kill the session, i.e. unset the users session id, the session cookie must also be deleted (assuming cookies are being used to propagate the session id), the session_destroy page from the PHP manual has some sample code on deleting the session cookie. Personally I've never found a need to destroy the session id/cookie as clearing the session data is enough.

More Resources

No comments:

Post a Comment