'Properly regenerating session ID's

I was looking at https://www.php.net/manual/en/function.session-regenerate-id.php because I wanted to learn how to properly deal with sessions and so I have used this example:

<?php
// NOTE: This code is not fully working code, but an example!
// my_session_start() and my_session_regenerate_id() avoid lost sessions by
// unstable network. In addition, this code may prevent exploiting stolen
// session by attackers.

function my_session_start() {
    session_start();
    if (isset($_SESSION['destroyed'])) {
       if ($_SESSION['destroyed'] < time()-300) {
           // Should not happen usually. This could be attack or due to unstable network.
           // Remove all authentication status of this users session.
           remove_all_authentication_flag_from_active_sessions($_SESSION['userid']);
           throw(new DestroyedSessionAccessException);
       }
       if (isset($_SESSION['new_session_id'])) {
           // Not fully expired yet. Could be lost cookie by unstable network.
           // Try again to set proper session ID cookie.
           // NOTE: Do not try to set session ID again if you would like to remove
           // authentication flag.
           session_commit();
           session_id($_SESSION['new_session_id']);
           // New session ID should exist
           session_start();
           return;
       }
   }
}

function my_session_regenerate_id() {
    // New session ID is required to set proper session ID
    // when session ID is not set due to unstable network.
    $new_session_id = session_create_id();
    $_SESSION['new_session_id'] = $new_session_id;
    
    // Set destroy timestamp
    $_SESSION['destroyed'] = time();
    
    // Write and close current session;
    session_commit();

    // Start session with new session ID
    session_id($new_session_id);
    ini_set('session.use_strict_mode', 0);
    session_start();
    ini_set('session.use_strict_mode', 1);
    
    // New session does not need them
    unset($_SESSION['destroyed']);
    unset($_SESSION['new_session_id']);
}
?>

I have tried to play with the example a bit and adding it but I have noticed one problem, whenever I log in the session gets wiped again and again, why?

The final look of the code is as follows:

<?php
// NOTE: This code is not fully working code, but an example!
// my_session_start() and my_session_regenerate_id() avoid lost sessions by
// unstable network. In addition, this code may prevent exploiting stolen
// session by attackers.

function my_session_start() {
    session_start();
    if (isset($_SESSION['destroyed'])) {
       if ($_SESSION['destroyed'] < time()-300) {
           // Should not happen usually. This could be attack or due to unstable network.
           // Remove all authentication status of this users session.
           remove_all_authentication_flag_from_active_sessions($_SESSION['ID']);
           throw(new DestroyedSessionAccessException);
       }
       if (isset($_SESSION['new_session_id'])) {
           // Not fully expired yet. Could be lost cookie by unstable network.
           // Try again to set proper session ID cookie.
           // NOTE: Do not try to set session ID again if you would like to remove
           // authentication flag.
           session_commit();
           session_id($_SESSION['new_session_id']);
           // New session ID should exist
           session_start();
           return;
       }
   }
}

function my_session_regenerate_id() {
    // New session ID is required to set proper session ID
    // when session ID is not set due to unstable network.
    $new_session_id = session_create_id();
    $_SESSION['new_session_id'] = $new_session_id;
    
    // Set destroy timestamp
    $_SESSION['destroyed'] = time();
    
    // Write and close current session;
    session_commit();

    // Start session with new session ID
    session_id($new_session_id);
    ini_set('session.use_strict_mode', 0);
    session_start();
    ini_set('session.use_strict_mode', 1);
    
    // New session does not need them
    unset($_SESSION['destroyed']);
    unset($_SESSION['new_session_id']);
}
my_session_start();
my_session_regenerate_id();
if(empty($_SESSION['ID'])) header("Location: /login/");
?>

Please, any details you need, ask me.



Solution 1:[1]

The issue with the example code in the php manual for session_regenerate_id() is the new session_id isn't in the data store yet, so when session_start() is called, there are no session variables to load.

I take care of this in the code below by setting a variable

$save_session_vars = $_SESSION;

to store the session vars and then upon starting the new session do the reverse:

$_SESSION = $save_session_vars;

You could use session_encode() and session_decode to do same thing instead.

Also worth noting in the regenerate_id sample code is

ini_set('session.use_strict_mode', 0);
session_start();
ini_set('session.use_strict_mode', 1);

The second ini_set will fail because a session is started. I do not believe this is even necessary as the strict_mode setting causes php to check if the session is on file when a user supplied session_id is used to start a session (exactly what we are doing and why strict_mode has been turned off). Once the session is started there is probably no harm in leaving strict_mode turned off. However, in the function below, I close the session again so that I can tag an initial access to the new session and as a regenerated session, so I go ahead and reset the strict_mode to 1 while the session is closed.

It's may be obvious, but I'll note that if for some reason you have more than one session_start() in your app, you need to replace all with the regen_session_start() so the destroyed flag gets checked.

<?php
function regenerate_session_id_unstable_networks() {

    // get a new session_id, then set this value on
    // current session + timestamp in case it is
    // accessed soon because of unstable network not
    // getting new cookie value from our upcoming change.
    // this value is checked in regen_session_start()
    // --------------------------------------------------
    $new_session_id = _sess_create_sid();
    $_SESSION['new_session_id'] = $new_session_id;
    $_SESSION['destroyed'] = time();
    //
    // Write and close current session;
    // ---------------------------------
    session_write_close();
    // We have to turn off strict mode here
    // because new session_id won't be on file
    // and we'll will get another session_id
    // from session_start()
    // --------------------------------------
    $was_in_strict_mode = false;
    if (1 == ini_get('session.use_strict_mode')) {
        ini_set('session.use_strict_mode', 0);
        $was_in_strict_mode = true;
    }
    // save our current session vars as they are
    // still set even though we closed the session
    // --------------------------------------------
    $save_session_vars = $_SESSION;
    // Start session with new session ID. By setting session_id
    // to our new session id we get a new empty session because
    // it's not stored yet, the same point as with ini setting.
    // --------------------------------------------------------
    $strict_mode = ini_get('session.use_strict_mode');
    session_id($new_session_id);
    regen_session_start();
    $cur_session_id = session_id();
    //
    // Restore our saved session vars
    // ------------------------------
    $_SESSION = $save_session_vars;
    //
    // and then remove the breadcrumbs left on old session from new session
    // --------------------------------------------------------------------
    unset($_SESSION['destroyed']);
    unset($_SESSION['new_session_id']);
    //
    // close session. reset strict mode and start session again
    // Note: must change ini settings while session is closed
    // --------------------------------------------------------
    session_write_close();
    if ($was_in_strict_mode) {
        @ini_set('session.use_strict_mode', 1);
    }
    // mark_new_session_as_regenerated_for_forensics();
    regen_session_start();
}


// -------------------------------
//
// -------------------------------
function regen_session_start() {
    $result = session_start();
    // if started session has been flagged destroyed,
    // either take evasive action or activate the new session
    // ------------------------------------------------------
    if (isset($_SESSION['destroyed'])) {

        // see if this access is after 5 minutes of change to session id
        // if so it's suspect so do something about it.
        // ---------------------------------------------------------------
        if ($_SESSION['destroyed'] + 300 < time()) {
            remove_all_authentication_flag_from_active_sessions($_SESSION['userid']);
            do_some_logging_and_take_some_action();
            throw(new DestroyedSessionAccessException);
        }
        // Hasn't been 5 minutes yet so not fully expired.
        // Could be because lost cookie by unstable network
        // (new session cookie value didn't get set on client).
        // Try again to set proper session ID cookie by closing session,
        // setting new session id, and calling session_start.
        // Sort of defeating purpose of key change so some security
        // risk in doing this for too long or at all, adjust to meet needs.
        // ----------------------------------------------------------------
        if (isset($_SESSION['new_session_id'])) {
            //increment_destroyed_access(session_id());
            session_commit();
            session_id($_SESSION['new_session_id']);
            // New session ID should exist
            session_start();
        }
    }
    return $result;
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 just-a-coder