Skip to main content
Add info.
Source Link

Is the following class well-built and secure or aream I missing something? Any improvement ideas welcome.

Is the following class well-built and secure or are I missing something?

Is the following class well-built and secure or am I missing something? Any improvement ideas welcome.

Review requests are for the post body, not the title
Link
Jamal
  • 34.9k
  • 13
  • 133
  • 237

Class for creating save sessions in PHP: Protection against session hijacking, better expiration and clean creation/destruction

Source Link

Class for creating save sessions in PHP: Protection against session hijacking, better expiration and clean creation/destruction

Is the following class well-built and secure or are I missing something?

/*
 * This class is for generating and using one single save session.
 */ 
final class Session {

    private static $name;
    private static $force_ssl_cookie;
    private static $destroy_unset;
    private static $max_inactivity;
    
    private static function checkInit() {
        if(!isset(self::$name)) {
            throw new Exception("Cannot use Session class without calling setSessionName once first.");
        }
        
        if(!isset(self::$force_ssl_cookie)) {
            throw new Exception("Cannot use Session class without calling setForceSSLCookie once first.");
        }
        
        if(!isset(self::$destroy_unset)) {
            throw new Exception("Cannot use Session class without calling setSessionDestroyUnset once first.");
        }
        
        if(!isset(self::$max_inactivity)) {
            throw new Exception("Cannot use Session class without calling setMaxInactivity once first.");
        }
    }
    
    public static function setName($name) {
        if(isset(self::$name)) {
            throw new Exception("Session class misuse: A session name has already been set.");
        }
        self::$name = $name;
    }
    
    public static function getName() {
        return self::$name;
    }
    
    public static function setForceSSLCookie($force_ssl_cookie) {
        if(isset(self::$force_ssl_cookie)) {
            throw new Exception("Session class misuse: The parameter force_ssl_cookie has already been set.");
        }
        self::$force_ssl_cookie = $force_ssl_cookie;
    }
    
    public static function setDestroyUnset($destroy_unset) {
        if(!is_array($destroy_unset)) {
            throw new Exception("Session class misuse: The parameter session_destroy_unset must be an array.");
        }
        
        if(isset(self::$destroy_unset)) {
            throw new Exception("Session class misuse: The parameter session_destroy_unset has already been set.");
        }
        self::$destroy_unset = $destroy_unset;
    }
    
    public static function setMaxInactivity($max_inactivity) {
        if(isset(self::$max_inactivity)) {
            throw new Exception("Session class misuse: The parameter max_inactivity has already been set.");
        }
        self::$max_inactivity = $max_inactivity;
    }
    
    /*
     * Destroy the current session and start a new one.
     */
    public static function restart() {
        self::destroyAbsolute();
        self::start();
    }
    
    /**
     * Secure start of a session.
     * 
     * @return type
     */
    public static function start() {
        
        self::checkInit();
        
        session_name(self::$name);
        session_set_cookie_params(0, COOKIEPATH, null, self::$force_ssl_cookie, true);
        
        $fresh = false;
        
        // no session existing, make a new session
        if(!isset($_COOKIE[self::$name])) {
            // replace the Session ID
            session_id(self::getSaveSessionId());
            $fresh = true;
        }
        
        session_start();

        // If the session was already existing, client_ip must match, the session must still be active etc.
        if (!$fresh) {
            // If this client ip does not match the ip of the current client, this might be an attack --> destroy session
            if (self::getClientIP() === $_SESSION["client_ip"] && self::getClientAgent() === $_SESSION['client_agent']) {
                if(self::checkStillActive()) {
                    return true;
                }
            } else {
                self::destroyAbsolute();
                header('Location: '.$_SERVER['REQUEST_URI']); 
                die;
            }
        } else {
            // if we have to create a new session, we do it in a secure, self-defined way
            self::setClientIP();
            self::setClientAgent();
            $_SESSION['last_activity'] = time();
            return true;
        }
    }

    // creates a new, secure session id (MUST be called BEFORE session_start())
    public static function getSaveSessionId() {
        self::checkInit();
        $hash_time = md5(microtime());
        $hash_ip = md5($_SERVER["REMOTE_ADDR"]);
        $hash_space = sha1(disk_free_space(getcwd()));
        return sha1($hash_time . $hash_ip . $hash_space);
    }


    /**
     * Secure instant destruction of session. Must be called after session_start !
     */
    public static function destroyAbsolute() {
        
        self::checkInit();
        
        session_name(self::$name);
        session_set_cookie_params(0, COOKIEPATH, null, self::$force_ssl_cookie, true);
        
        if(session_id()) {
            if (isset($_COOKIE[session_name()])) {
                setcookie(session_name(), "", time() - 42000, COOKIEPATH);
            }
            unset($_COOKIE[session_name()]);
            session_destroy();
        }
    }

    // retrieve the current session id
    public static function getCurrentId() {
            self::checkInit();
            session_name(self::$name);
            return session_id();
    }

    /**
     * Destroys a session with a specific ID.
     * 
     * @param type $sessid: Session ID.
     */
    public static function destroySpecific($sessid) {
        if($sessid && !empty($sessid)) {
            session_name(self::$name);
            session_id($sessid);
            // session_start();
            foreach(self::$destroy_unset as $uns) {
                unset($_SESSION[$uns]);
            }
            session_destroy();
        }
    }

    public static function checkStillActive() {
        
        self::checkInit();
        
        // make sure that a user was not inactive for too long
        if(intval($_SESSION['last_activity']) < time()-self::$max_inactivity) { //have we expired?
            self::destroyAbsolute();
            header('Location: '.$_SERVER['REQUEST_URI']); 
            die;
        } else { //if we haven't expired:
            $_SESSION['last_activity'] = time(); //this was the moment of last activity.
            return true;
        }
    }
    
    // sets the session IP of the current client
    private static function setClientIP() {
            if (isset($_SERVER["HTTP_X_FORWARDED_FOR"])) {
                    $_SESSION["client_ip"] = md5($_SERVER["HTTP_X_FORWARDED_FOR"]);
            } else {
                    $_SESSION["client_ip"] = md5($_SERVER["REMOTE_ADDR"]);
            }
    }

    // retrieves the client IP of the session owner
    private static function getClientIP() {
            if (isset($_SERVER["HTTP_X_FORWARDED_FOR"])) {
                    return md5($_SERVER["HTTP_X_FORWARDED_FOR"]);
            } else {
                    return md5($_SERVER["REMOTE_ADDR"]);
            }
    }

    private static function setClientAgent() {
        $_SESSION['client_agent'] = md5($_SERVER['HTTP_USER_AGENT']);
    }

    private static function getClientAgent() {
        return md5($_SERVER['HTTP_USER_AGENT']);
    }
    
}

Example of usage:

Session::setName("SESSION-mysession");
Session::setForceSSLCookie($force_ssl_cookie);
Session::setMaxInactivity(PHP_INT_MAX); // no problem so far
Session::setDestroyUnset(array('example'));

// create a new session, if no session is initialized
Session::start();