Avoiding frustration with PHP Sessions

Posted on Tuesday, Mar 13, 2007 at 10:56 AM in

PHP's support for sessions make adding "state" to your web application super easy.  Bus because the illusion of state is maintained by storing a Session ID via a user's cookies, you might find yourself losing potentially productive hours chasing down bizarre client side bugs or opening up a potential security hole.  Here are 4 tips to help you avoid wasting your time and securing your site.

1. Don't use underscores in host names

Unless you've memorized the RFC for allowed characters in a host name, you may not be aware that underscores are not allowed in host names.  Some browsers, like Firefox, don't enforce this prohibition, but Internet Explorer enforces it and will refuse to set a cookie belonging to a host name with an underscore in it.  IE will instead cause php to generate a new session id for the visitor on each page load, since, the user never accepts a session id. Source: set_cookie documentation.

2. Commit your sessions before redirects

You should call session_write_close (or its alias session_commit) to write session data before issuing an HTTP Location redirects.  This also "frees" the user's session so that they can do other activities in your web app.  See the comments by cenaculo and bkatz.

3. Prevent session fixation

Session fixation allows an attacker to get a valid session id without predicting it or reading it from a user's cookies or $_GET array.  Instead, a victim ends up using a session id generated by the attacker not your web server.  You can prevent this by calling session_regenerate_id(), particularly after storing sensitive information such as a login name or flag.  This should render the attacker's defined sessin id useless.   Chris Shifflet has a more thorough discussion of Session Fixation.

4. Don't expose session_id's

Cookies are, relatively, a more secure place to store your session ids compared to embedding them as a parameter in the query string. There are two ini setting to control this behavior, and which one is appropriate is hard to tell, so you should set them both.  You should set session.use_trans_sid to 0 (off) and if you're using 4.3.0 or higher you can set session.use_only_cookies to "1".

Comments

Erik Friend says

5. Use a shared session data directory when serving pages from multiple web servers. This will
drive you crazy if you don't do it. If your web site is served by multiple load-balanced web
servers, then the session will be lost when the client hits any machine other than the initial
machine that created the session. This problem will manifest itself as intermittent complaints that
'...people keep getting logged out for no reason.' Unless your load balancing mechanism
prevents clients from jumping between different machines in your server farm, you must configure
each machine to store session data in a central location that all the servers can reach. The php
function session_save_path() can be used to set the location. Try using an nfs mount or samba share
as the data directory. Alternatively, try storing session data in a database. For the advanced
programmer, try writing your own session handler using the session_set_save_handler() function.
Once you get all your servers storing their sessions in the same place, your users will no longer
be plagued by mysterious session drop outs (at least not ones caused by this issue) Good luck!
Posted Tuesday, Mar 13, 2007 at 06:56 PM

Jack Sleight says

Good article, although one more point: 6. When creating new sessions, record the users IP address
and user agent header, then check that these are the same on every request. Although not completely
bullet proof, this goes a long way to protecting against session hijacking. Also, as Erik Friend
said, store the session data in a database. If you write your own session handling functions/class
implementing this, and the extra checks I mentioned are pretty straightforward, and as the data is
in the database rather than the file system, it protects against people snooping in the session
data folder.
Posted Thursday, Mar 15, 2007 at 04:13 PM

Brendan Falkowski says

Excellent primer for PHP security best practices. I've noticed a lot more information about
session fixation attacks popping up recently: "http://www.acros.si/papers/session_fixation.pdf">http://www.acros.si/papers/session_fixation.pdf
and "http://shiflett.org/articles/session-fixation">http://shiflett.org/articles/session-fixation.
I'm working on implementing #4 and Jack's #6 for a client right now, but I was surprised to
see the comment by Erik (#5) about load-balanced web servers and session handling. I've never
had a chance to get into that but it seems interesting. For anybody looking for a general
introduction to PHP sessions and HTTP headers, check out this "http://shiflett.org/articles/the-truth-about-sessions">article. Very easy reading, but
detailed information and examples.
Posted Saturday, Mar 24, 2007 at 04:53 PM

Torsten Roehr says

Storing session data in the database can easily be achieved with PEAR::HTTP_Session:
http://pear.php.net/package/HTTP_Session
Posted Tuesday, Apr 3, 2007 at 05:46 AM

Alessandro Ronchi says

Your "Don't use underscores in host names" hint avoided me an headache. Thank you!
Posted Wednesday, May 23, 2007 at 01:22 PM

Kevin foster says

"Don't use underscores in host names" I've been trying to figure out why sessions
wont persist in IE for the past few hours. Thanks allot!
Posted Thursday, Sep 27, 2007 at 12:16 AM

Erik Friend says

5. Use a shared session data directory when serving pages from multiple web servers. This will drive you crazy if you don't do it. If your web site is served by multiple load-balanced web servers, then the session will be lost when the client hits any machine other than the initial machine that created the session. This problem will manifest itself as intermittent complaints that '...people keep getting logged out for no reason.' Unless your load balancing mechanism prevents clients from jumping between different machines in your server farm, you must configure each machine to store session data in a central location that all the servers can reach. The php function session_save_path() can be used to set the location. Try using an nfs mount or samba share as the data directory. Alternatively, try storing session data in a database. For the advanced programmer, try writing your own session handler using the session_set_save_handler() function. Once you get all your servers storing their sessions in the same place, your users will no longer be plagued by mysterious session drop outs (at least not ones caused by this issue) Good luck!

7. include function libs BEFORE calling session_start()

This way, you can store complex user-defined objects in a session. Including your class libraries first allows php to recognize your class amidst other session data. Your object will retain all of its properties and its methods will be available directly through the session super-global.

ex: $food_list = $_SESSION['food']-> get_list();

WARNING: Starting the session prior to library inclusion causes php to misinterpret your object. This usually triggers a php crash. Additionally, php will not output error text to inform you of problem. Sometimes, the object still resides in the session, however, its class name will have changed and its methods are not functional.

Here is some source code to illustrate

food.inc

class food 
{
    var $menu;
    function food ()
    {
        $this->menu = array(); 
    }

    function add ($name) 
    {
         return $this->menu[] = $name; 
    }
    
    function get_list () 
    {
        return $this->menu; 
    }
}
?>

index.php

error_reporting(E_ALL);
include('food.inc');
session_start();
# first page load:
# session should be empty, so create a new object instance 
if (!isset($_SESSION['food'])) 
{
   # initialize a 'food' 
   object $food = new food(); 
   $food->add('pizza');
   $food->add('rolls');
   echo join('', $food->get_list());
   # store the object in the session
   $_SESSION['food'] = $food;
}
# all other page loads: session contains object 
else 
{ # use object from session
   $_SESSION['food']->add('salad');
   $_SESSION['food']->add('drpepper'); 
   echo join('
', $_SESSION['food']->get_list()); } ?>

To demonstrate, try exchanging the order of the session_start and include calls.

Posted Friday, Nov 2, 2007 at 02:45 AM

mehmet poyraz says

thank you for your help my friend
Posted Saturday, Dec 15, 2007 at 06:04 PM

tommyg says

biggest help ever,
i added
session_write_close();
to my code and its fine. thanks for this great article
Posted Saturday, Jan 5, 2008 at 12:03 PM

esanjor says

Many thanks for the great information guys.
Posted Monday, Jul 7, 2008 at 05:41 AM

Arasta says

Thanks guys. Great info
Posted Monday, Jul 7, 2008 at 05:43 AM

John Pancakes says

Some constructive criticism about "Jack Sleight's" #6 post.

"Good article, although one more point: 6. When creating new sessions, record the users IP address
and user agent header, then check that these are the same on every request. Although not completely
bullet proof, this goes a long way to protecting against session hijacking."

I don't think this is a good idea. Services like AOL will change a user's IP throughout their AOL session. Other users may have dynamic IP's as well. If this idea was implemented a large chunk of your users would lose the session and be logged out fairly regularly.
Posted Wednesday, Sep 3, 2008 at 10:51 PM

Post your comment

Required but will not be shown
URL for your own blog or site - begin with http or https.
Most HTML is allowed.
The values you submit will be saved to a cookie to automatically fill in this form.
 Yes, save it.