CVE-2022-31888 (PHP, published 2023-04-05T22:15Z)
Description: value: Session Fixation vulnerability in in function login in class.auth.php in osTicket through 1.16.2.
Severity: nan (CVSS v3 base score: 8.8)
Weakness: CWE-384 \u2014 Session Fixation
Authenticating a user, or otherwise establishing a new user session, without invalidating any existing session identifier gives an attacker the opportunity to steal authenticated sessions.
Repository: https://github.com/osTicket/osTicket
Files changed:
include/class.auth.php
include/class.usersession.php
open.php
Commit message: Security: Session Fixation

This commit addresses possible session fixation on both agent and end user
login. Upon login users are now issued new session with the old one
invalidated.


Vulnerable code:

        $staff->refreshSession(true); //set the hash.


        $user->refreshSession(true); //set the hash.


   var $userID='';
   var $validated=FALSE;

   function __construct($userid){
      $this->browser=(!empty($_SERVER['HTTP_USER_AGENT'])) ? $_SERVER['HTTP_USER_AGENT'] : $_ENV['HTTP_USER_AGENT'];
      $this->ip=(!empty($_SERVER['REMOTE_ADDR'])) ? $_SERVER['REMOTE_ADDR'] : getenv('REMOTE_ADDR');
      $this->session_id=session_id();
      $this->userID=$userid;
   function isStaff(){
       return FALSE;
       return FALSE;

   function getSessionId(){
   function getIP(){
   function getBrowser(){
   function refreshSession(){
       //nothing to do...clients need to worry about it.
   }


      return($token);
       @list($hash,$expire,$ip)=explode(":",$htoken);
        global $cfg;
        $token = rawurldecode($htoken);
        #check if we got what we expected....
        if($token && !strstr($token,":"))
            return FALSE;
        #get the goodies
        list($hash,$expire,$ip)=explode(":",$token);
        #Make sure the session hash is valid
        if((md5($expire . SESSION_SECRET . $this->userID)!=$hash)){
            return FALSE;
        }
        #is it expired??
        if($maxidletime && ((time()-$expire)>$maxidletime)){
            return FALSE;
        }
        if($checkip && strcmp($ip, MD5($this->ip)))
            return FALSE;
        $this->validated=TRUE;
        return TRUE;
        return FALSE;
class ClientSession extends EndUser {

    function __construct($user) {
        parent::__construct($user);
        $this->token = &$_SESSION[':token']['client'];
        // XXX: Change the key to user-id
        $this->session= new UserSession($user->getId());
    }

    function getSessionUser() {
        return $this->user;
    }

    function isValid(){
        global $_SESSION,$cfg;

        if(!$this->getId() || $this->session->getSessionId()!=session_id())
            return false;

        return $this->session->isvalidSession($this->token,$cfg->getClientTimeout(),false)?true:false;
    }

    function refreshSession($force=false){
        global $cfg;

        //TODO: separate expire time from hash??
        osTicketSession::renewCookie($time, $cfg->getClientSessionTimeout());
    function getIP(){
class StaffSession extends Staff {
    var $session;
    var $token;
        if ($staff = parent::lookup($var)) {
            $staff->token = &$_SESSION[':token']['staff'];
            $staff->session= new UserSession($staff->getId());
        if (!isset($_SESSION['_auth']['staff']['2fa']))
            return false;

        return true;
    }

    function isValid(){
        global $cfg;

        if(!$this->getId() || $this->session->getSessionId()!=session_id())
            return false;

        if ($this->is2FAPending())
            return false;

        return $this->session->isvalidSession($this->token,$cfg->getStaffTimeout(),$cfg->enableStaffIPBinding())?true:false;
    }

    function refreshSession($force=false){
        global $cfg;

        $time = $this->session->getLastUpdate($this->token);
        // Deadband session token updates to once / 30-seconds
        if (!$force && time() - $time < 30)
            return;

        $this->token=$this->getSessionToken();

        osTicketSession::renewCookie($time, $cfg->getStaffSessionTimeout());
    }

    function getSession() {
        return $this->session;
    }

    function getSessionToken() {
        return $this->session->sessionToken();
    function getIP(){
        return $this->session->getIP();


        if($thisclient && $thisclient->isValid()) {
            session_regenerate_id();
            session_write_close();

Fixed code:
        // Regenerate session id and refresh (avoid session fixation)
        $staff->regenerateSession();
        // Set session token
        $staff->setSessionToken();
        // Set Auth Key
        // Regenerate session id and refresh (avoid session fixation)
        $user->regenerateSession();
        // Set Session Token
        $user->setSessionToken();
   var $userID = 0;
   var $validated = false;
   function __construct($userid) {
      $this->browser = (!empty($_SERVER['HTTP_USER_AGENT'])) ? $_SERVER['HTTP_USER_AGENT'] : $_ENV['HTTP_USER_AGENT'];
      $this->ip = (!empty($_SERVER['REMOTE_ADDR'])) ? $_SERVER['REMOTE_ADDR'] : getenv('REMOTE_ADDR');
      $this->session_id = session_id();
      $this->userID = $userid;
   function isStaff() {
       return false;
       return false;
   function getSessionId() {
   function getIP() {
   function getBrowser() {
      return $token;
       @list($hash, $expire, $ip) = explode(":", $htoken);
        global $cfgi;
        // Compare session ids
        if (strcmp($this->getSessionId(), session_id()))
            return false;
        // Is the session invalidated?
        if (isset($_SESSION['KAPUT']) &&  $_SESSION['KAPUT'] < time())
            return (session_destroy() && false);
        $token = rawurldecode($htoken);
        // Check if we got what we expected....
        if ($token && !strstr($token,":"))
            return false;
        // Get the goodies
        list($hash, $expire, $ip) = explode(":",$token);
        // Make sure the session hash is valid
        if ((md5($expire . SESSION_SECRET . $this->userID) != $hash))
            return false;

        // is it expired??
        if ($maxidletime && ((time()-$expire)>$maxidletime))
            return false;
        if ($checkip && strcmp($ip, MD5($this->ip)))
            return false;

        $this->validated = true;
        return true;
   }

   function regenerateSession($destroy=false) {
       // Delayed kaput time for current session
       $_SESSION['KAPUT'] = time() + 60;
       // Save the session id as old
       $old = session_id();
       // Regenerate the session without destroying data
       session_regenerate_id(false);
       // Get new session id and close
       $new = session_id();
       session_write_close();
       // Start new session
       session_id($new);
       session_start();
       $this->session_id  = $new;
       // Make sure new session is not set to KAPUT
       unset($_SESSION['KAPUT']);
       // Destroy ?
       if ($destroy) {
           // Destrory old session
           $this->destroySession($old);
           // Restore new session
           session_id($new);
           session_start();
       }
       return true;
   }
   function destroySession($id) {
       // Close current session
       session_write_close();
       // Start target session
       session_id($id);
       session_start();
       // Destroy session
       session_destroy();
       session_write_close();
       return true;
        return  ($this->validated);
trait UserSessionTrait {
    // Session Object
    // Session Token
    // Maximum idle time before session is considered invalid
    var $maxidletime = 0;
    // Indicates if session is bound to the IP address
    var $checkip = false;
    // User class
    var $class = '';

    function refreshSession($force=false) {
        osTicketSession::renewCookie($time, $this->maxidletime);
    }
    function regenerateSession($destroy=false) {
        $this->session->regenerateSession($destroy);
        // Set cookie for the new session id.
        $this->refreshSession(true);
    function setSessionToken($token=null) {
        // Assign memory to token variable
        $this->token = &$_SESSION[':token'][$this->class];
        // Set token
        $this->token = $token ?: $this->getSessionToken();
    }

    function getIP() {

    function isValidSession() {
        return ($this->getId()
                && $this->session->isvalidSession($this->token,
                    $this->maxidletime, $this->checkip));
    }

    abstract function isValid();
class ClientSession extends EndUser {
    use UserSessionTrait;
    function __construct($user) {
        global $cfg;
        parent::__construct($user);
        $this->class ='client';
        // XXX: Change the key to user-id
        $this->session = new UserSession($user->getId());
        $this->setSessionToken();
        $this->maxidletime = $cfg->getClientTimeout();
    }
    function getSessionUser() {
        return $this->user;
    }

    function isValid() {
        return $this->isValidSession();
    }
}

class StaffSession extends Staff {
    use UserSessionTrait;
        global $cfg;
        if (($staff = parent::lookup($var))) {
            $staff->class = 'staff';
            $staff->session = new UserSession($staff->getId());
            $staff->setSessionToken();
            $staff->maxidletime = $cfg->getStaffTimeout();
            $staff->checkip = $cfg->enableStaffIPBinding();
        unset($_SESSION['_auth']['staff']['2fa']);
        return isset($_SESSION['_auth']['staff']['2fa']);
    function isValid() {
        return (!$this->is2FAPending() && $this->isValidSession());
        if ($thisclient && $thisclient->isValid()) {
            // Regenerate session id
            $thisclient->regenerateSession();