0

How do I differentiate between a new session request and a request for a session that has been removed by the garbage collector? This is not a question about whether the current session is new or continued: session_status() tells me that.

I have a multipart form. The customer progresses from part 1 to part 2 and 3. My $_SESSION array is building. Before progressing to part 4, the customer has a long phone call. An hour or so later he returns to the form. session_status() returns PHP_SESSION_NONE, but the customer expects I still know what he has entered in the previous parts.

I suppose in this case the payload of the request contains a session_id that no longer exists. Perhaps I can compare the OLD session_id to the NEW session_id. If I could do that, even before calling session_start(), I can return to the customer: "Sorry dude, I know you have already filled out part of the form, but I have lost all memory to your input due to inactivity. You have to start all over again."

A new customer also has an empty $_SESSION array. In this case, session_status() also returns PHP_SESSION_NONE, but this customer needs a welcome! message.

Erik
  • 135
  • 1
  • 6
  • Does this answer your question? [PHP: how to detect if a session has expired automatically?](https://stackoverflow.com/questions/41165900/php-how-to-detect-if-a-session-has-expired-automatically) – Nico Haase Sep 11 '20 at 06:21
  • 1
    Situation where forms are allowed to be filled across multiple pages should have mechanism to persist data independent from session.... https://stackoverflow.com/questions/22371630/persist-form-data-across-multiple-steps-in-laravel – kewlashu Sep 11 '20 at 06:33
  • @Nico Haase: that question is about predicting the end of a session lifetime. In my case I don't know if there is a session to begin with. The request may be from client A of client B. One is a completely new customer entering the form sequence, the other a customer already in the middle of answering questions but lost the session variables in the meantime due to inactivity. – Erik Sep 11 '20 at 08:23
  • Yeah. Session content is lost. That's it - what else are you looking for? – Nico Haase Sep 11 '20 at 09:41
  • Re-read my question or my own solution. – Erik Sep 11 '20 at 13:18

2 Answers2

2

There is no way to differentiate that. You can't compare to any "old id" if that old id has been garbage collected and doesn't exist anymore. If that id still existed in some form or another, you wouldn't have to solve that problem to begin with. Your server must simply treat non-existing session ids as non-existent and not try to do much with them, as that is one vector for session fixation attacks.

You can simply display a generic message that matches all cases, something along the lines of:

Invalid session.

Your session may have expired due to inactivity or you have followed an invalid link. Try restarting the wizard process from the beginning. (Link here)

If that's a common inconvenience for your customers, consider extending the session lifetime so this occurs less frequently.

deceze
  • 510,633
  • 85
  • 743
  • 889
  • the client must present at each request the session_id for the PHP session to continue with. Perhaps this is the the $_SERVER global. How else can the webserver tell which session is yours or mine? – Erik Sep 11 '20 at 08:07
  • Do you know how sessions work? The server generates a random id. It creates a file with that id server-side and stores data in it. That data is loaded to and serialised from `$_SESSION`. The server sets a cookie containing that id, which is sent to the browser. The browser sends that cookie back with every request, allowing the server to load the appropriate session file on `session_start()`. That's all. That's how it knows which session is yours. If the session expires and the session file is deleted, then the cookie id—if it still exists—becomes meaningless. – deceze Sep 11 '20 at 08:48
  • Again, you can't tell whether the session file with the id in the cookie doesn't exist *anymore*, or whether the client has just sent you an arbitrary cookie with some id which *never* existed. There's just simply no session file matching that cookie id, period. Doing anything with arbitrary non-existing session ids obtained from a client can lead to security problems known as session fixation. Read the aforelinked article about that. – deceze Sep 11 '20 at 08:51
  • I think I may have a solution on my Apache/PHP implementation on an Ubunu 20.04 box. I'll answer my own question later; details and side-notes in that answer – Erik Sep 11 '20 at 10:21
0

I have found the solution for my setup: Apache/PHP on an Ubuntu 20.04 box. Details of the machine are further below.

Pre-requisites:

  1. On a new connection to your server $_SERVER['HTTP_COOKIE'] is not set. This solution relies fully on that fact. More research is needed to ascertain this always works the same on every installation. Your box may behave differently, so check it!
  2. Once the session is created, write a known variable to the session. It is always wise to do so, to make sure the session file's last modified timestamp is reset each time the script is called. This prevents the garbage collector from removing the session file prematurely.

To do:

  1. Create a php file like the one below and include/require that file on every PHP request to your server.
  2. Once required/included: examine $_SESSION['state'] and act accordingly

Script:

<?php
   #  To eventually use it, remove all output commands like print_r, echo, print, etc.
   #  For testing purposes this is OK.

   #  Helpful to know, you may uncomment:
   #  echo 'maxlifetime : ' . ini_get('session.gc_maxlifetime') . ' seconds.' . PHP_EOL;
   #  echo 'save_path   : ' . ini_get('session.save_path') . PHP_EOL;

   #  Serves output in fixed font. Uncomment/remove as you see fit
   #  echo '<pre>';

   #  Start a NEW session or load an EXISTING one
   if (!session_id()) {
      session_start();
   }

   #  If the session is a new one, variable $_SESSION['moment'] is not yet set
   if (!isset($_SESSION['moment'])) {
      #  The variable was not set, check if $_SERVER['HTTP_COOKIE'] was set or not
      if (!isset($_SERVER['HTTP_COOKIE'])) {
         echo 'New connection ('.session_id().'), empty session.' . PHP_EOL . 'Session state: NEW.' . PHP_EOL;
         $_SESSION['state'] = 'NEW';
      }
      else {
         echo 'Existing connection ('.session_id().'), but empty session.' . PHP_EOL . 'Session state: EXPIRED.' . PHP_EOL;
         $_SESSION['state'] = 'EXPIRED';
      }
   }
   #  The variable was set, the session file was succesfully read this is a continued session
   else {
      echo 'Existing connection ('.session_id().'), but variable $_SESSION[moment] exists: ' . $_SESSION['moment'] . '.' . PHP_EOL . 'Session state: CONTINUED.' . PHP_EOL;
      $_SESSION['state'] = 'CONTINUED';
   }

   #  Always write something to the session file to change the last modified timestamp
   $_SESSION['moment'] = Date("Y-m-d H:i:s");
?>

Output on a new connection:

New connection (qvgo4lfd9cibrqbmgngmp1gto1), empty session.
Session state: NEW.

Output on a refresh from the browser:

Existing connection (qvgo4lfd9cibrqbmgngmp1gto1), but variable $_SESSION[moment] exists: 2020-09-11 15:16:10.
Session state: CONTINUED.

Output after session has gone:

Existing connection (qvgo4lfd9cibrqbmgngmp1gto1), but empty session.
Session state: EXPIRED.

I also uploaded the script to my production box (Ubuntu 18.04). Identical results.

Checked on Windows 10:

  • Internet Explorer 11.1082.18362.0
  • Chrome 85.0.4183.102
  • Firefox 67.0
  • Edge 85.0.564.51
  • Avast 85.0.5675.84

Checked on MacOS Catalina (85.0.5675.84)

  • Chrome 85.0.4183.102
  • Safari 13.1.2 (15609.3.5.1.3)

About the servers:

Apache

Server version: Apache/2.4.41 (Ubuntu)
Server built:   2020-08-12T19:46:17

PHP

PHP 7.4.3 (cli) (built: May 26 2020 12:24:22) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
    with Zend OPcache v7.4.3, Copyright (c), by Zend Technologies

Ubuntu

Distributor ID: Ubuntu
Description:    Ubuntu 20.04.1 LTS
Release:    20.04
Codename:   focal
Erik
  • 135
  • 1
  • 6