Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to properly end a users session?

I've been working on the security of my site (PHP) and there's a ton of information to ingest. I've tried to implement security I've researched on OWASP, but one thing I'm a little nervous about, among other things, is how to handle SESSIONS when the user logs out.

Currently all I'm using is:

session_destroy();

But, I've read that I should change the XRSF token and start another SESSION so it forces the user to resubmit login credentials in-turn explicitly ending the users SESSION.

Is session_destroy() enough?

EDIT

I've downloaded michael-the-messenger, which I believe was created by Michael Brooks (Rook) which should be VERY secure, and I saw some code that I might want to use. Is this something that could safely replace the session_destroy() I'm using?

CODE

if($_SESSION['user']->isAuth())
{
    /* if they have clicked log out */
    /* this will kill the session */
    if($_POST['LogMeOut'] == 'true')
    {
        //When the user logs out the xsrf token changes.
        $tmp_xsrf = $_SESSION['user']->getXsrfToken();
        $_SESSION['user']->logout();
        $loginMessage = str_replace($tmp_xsrf, $_SESSION['user']->getXsrfToken(), $loginMessage);
        print layout('Authorization Required', $loginMessage);
    }
    else
    {
        header("Location: inbox.php");
        //user is allowed access. 
    }
}
else
{
    // code goes on ....

LOGOUT

public function logout()
{
    $_SESSION['user'] = new auth();
}

Obviously $_SESSION['user'] = new auth(); reinstantiates the object which sets a private variable $auth to false.

like image 288
Mike Avatar asked Nov 25 '22 00:11

Mike


1 Answers

but one thing I'm a little nervous about, among other things, is how to handle SESSIONS when the user logs out.

According to manual:

In order to kill the session altogether, like to log the user out, the session id must also be unset. If a cookie is used to propagate the session id (default behavior), then the session cookie must be deleted. setcookie() may be used for that.

So, in order to safely destroy a session, we'd also erase it on the client-machine.

session_destroy() along with setcookie(session_name(), null, time() - 86400) will do that.

Apart from that,

What you are doing wrong and why:

Session storage merely uses data serialization internally. By storing an object in the $_SESSION superglobal you just do serialize/unserialize that object on demand without even knowing it.

1) By storing an object in $_SESSION you do introduce global state. $_SESSION is a superglobal array, thus can be accessed from anywhere.

2) Even by storing an object that keeps an information about logged user, you do waste system memory. The length of object representation is always greater than a length of the strings.

But why on earth should you even care about wrapping session functionality? Well,

  • It makes a code easy to read, maintain and test
  • It adheres Single-Responsibility Principle
  • It avoids global state (if properly used), you'll access session not as $_SESSION['foo'], but $session->read['foo']
  • You can easily change its behaivor (say, if you decide to use DB as session storage) without even affecting another parts of your application.
  • Code reuse-ability. You can use this class for another applications (or parts of it)

If you wrap all session-related functionality into a signle class, then it will turn into attractive:

$session = new SessionStorage();

$session->write( array('foo' => 'bar') );

if ( $session->isValid() === TRUE ) {

    echo $session->read('foo'); // bar

} else {

    // Session hijack. Handle here
}

// To totally destroy a session:
$session->destroy();


// if some part of your application requires a session, then just inject an instance of `SessionStorage`
// like this:
$user = new Profile($session);


// Take this implementation as example:

final class SessionStorage
{
    public function __construct()
    {
        // Don't start again if session is started:
        if ( session_id() != '' ) {
            session_start();
        }

        // Keep initial values
        $_SESSION['HTTP_USER_AGENT'] = $_SERVER['HTTP_USER_AGENT'];
        $_SESSION['REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'];
    }

    /**
     * You can prevent majority of hijacks using this method
     * 
     * @return boolean TRUE if session is valid
     */
    public function isValid()
    {
        return $_SESSION['HTTP_USER_AGENT'] === $_SERVER['HTTP_USER_AGENT'] && $_SESSION['REMOTE_ADDR'] === $_SERVER['REMOTE_ADDR'] ;
    }


    public function __destruct()
    {
        session_write_close();
    }

    /**
     * Fixed session_destroy()
     * 
     * @return boolean
     */
    public function destroy()
    {
        // Erase the session name on client side
        setcookie(session_name(), null, time() - 86400);

        // Erase on the server
        return session_destroy();
    }


    public function write(array $data)
    {
        foreach($data as $key => $value) {
            $_SESSION[$key] = $value;
        }
    }


    public function exists()
    {
        foreach(func_get_args() as $arg){

            if ( ! array_key_exists($arg, $_SESSION) ){
                return false;
            }
        }

        return true;
    }

    public function read($key)
    {
        if ( $this->exists($key) ){

            return $_SESSION[$key];

        } else {

            throw new RuntimeException('Cannot access non-existing var ' .$key);
        }
    }

}
like image 140
Yang Avatar answered Dec 05 '22 20:12

Yang