PHP会话和session_regenerate_id

时间:2015-07-24 06:40:24

标签: php security session

我一直试图解决这个问题好几周了 我已经创建了一个类来保护PHP会话,除非有人试图执行注册(问题#2)并且某些功能被禁用(导致#2发生),否则它会正常工作,其余的网站工作得很好。

以下是问题:

  1. session_regenerate_id - 注释掉 从这里开始,一切都运行得很好,除了验证码创建机制(仅在注册页面上),对于登录页面它工作得很好
  2. session_regenerate_id(true) - 取消注释
    在这里,注册工作正常,没有问题,没有验证码问题,但在几次刷新页面后,会话刚刚消失,因此用户需要登录6次以及$ _SESSION再次设置为null
  3. 我知道问题可能在哪里,但我不知道如何解决它。

    我有一个私有静态函数,在调用 session_start()后直接调用

    private static function GenerateSessionData()
    {
        $_SESSION['loggedin'] = '';
        $_SESSION['username'] = '';
        $_SESSION['remember_me'] = '';
        $_SESSION['preferredlanguage'] = '';
        $_SESSION['generated_captcha'] = '';
    }
    

    这样做是为了预先定义会话使用的变量(我90%确定这就是会话变为空白的原因)。
    我不确定的是,为什么。

    以下是完整的会话课程:

    <?php
    
    Class Session
    {
    
        public static $DBConnection;
        private static $SessionCreated = false;
    
        public function __construct($Database)
        {
            session_set_save_handler(array($this, 'Open'), array($this, 'Close'), array($this, 'Read'), array($this, 'Write'), array($this, 'Destroy'), array($this, 'GarbageCollector'));
            register_shutdown_function('session_write_close');
            Session::$DBConnection = $Database::$Connection;
        }
    
        private static function GenerateSessionData()
        {
            $_SESSION['loggedin'] = '';
            $_SESSION['username'] = '';
            $_SESSION['remember_me'] = '';
            $_SESSION['preferredlanguage'] = '';
            $_SESSION['generated_captcha'] = '';
        }
    
        public static function UpdateSession($Data)
        {
            if(!isset($_SESSION['loggedin']))
                Session::GenerateSessionData();
            foreach($Data as $key=>$value)
                $_SESSION[$key] = $value;
        }
    
        public static function GenerateCSRFToken()
        {
            $InitialString = "abcdefghijklmnopqrstuvwxyz1234567890";
            $PartOne = substr(str_shuffle($InitialString),0,8);
            $PartTwo = substr(str_shuffle($InitialString),0,4);
            $PartThree = substr(str_shuffle($InitialString),0,4);
            $PartFour = substr(str_shuffle($InitialString),0,4);
            $PartFive = substr(str_shuffle($InitialString),0,12);
            $FinalCode = $PartOne.'-'.$PartTwo.'-'.$PartThree.'-'.$PartFour.'-'.$PartFive;
            $_SESSION['generated_csrf'] = $FinalCode;
            return $FinalCode;
        }
    
        public static function ValidateCSRFToken($Token)
        {
            if(isset($Token) && $Token == $_SESSION['generated_csrf'])
            {
                unset($_SESSION['generated_csrf']);
                return true;
            }
            else
                return false;
        }
    
        public static function UnsetKeys($Keys)
        {
            foreach($Keys as $Key)
                unset($_SESSION[$Key]);
        }
    
        public static function Start($SessionName, $Secure)
        {
            $HTTPOnly = true;
            $Session_Hash = 'sha512';
    
            if(in_array($Session_Hash, hash_algos()))
                ini_set('session.hash_function', $Session_Hash);
            ini_set('session.hash_bits_per_character', 6);
            ini_set('session.use_only_cookies', 1);
    
            $CookieParameters = session_get_cookie_params();
    
            session_set_cookie_params($CookieParameters["lifetime"], $CookieParameters["path"], $CookieParameters["domain"], $Secure, $HTTPOnly);
            session_name($SessionName);
            session_start();
            session_regenerate_id(true);
            if(!Session::$SessionCreated)
                if(!isset($_SESSION['loggedin']))
                    Session::GenerateSessionData();
            Session::$SessionCreated = true;
        }
    
        static function Open()
        {
            if(is_null(Session::$DBConnection))
            {
                die("Unable to establish connection with database for Secure Session!");
                return false;
            }
            else
                return true;
        }
    
        static function Close()
        {
            Session::$DBConnection = null;
            return true;
        }
    
        static function Read($SessionID)
        {
            $Statement = Session::$DBConnection->prepare("SELECT data FROM sessions WHERE id = :sessionid LIMIT 1");
            $Statement->bindParam(':sessionid', $SessionID);
            $Statement->execute();
            $Result = $Statement->fetch(PDO::FETCH_ASSOC);
            $Key = Session::GetKey($SessionID);
            $Data = Session::Decrypt($Result['data'], $Key);
            return $Data;
        }
    
        static function Write($SessionID, $SessionData)
        {
            $Key = Session::GetKey($SessionID);
            $Data = Session::Encrypt($SessionData, $Key);
    
            $TimeNow = time();
    
            $Statement = Session::$DBConnection->prepare('REPLACE INTO sessions (id, set_time, data, session_key) VALUES (:sessionid, :creation_time, :session_data, :session_key)');
            $Statement->bindParam(':sessionid', $SessionID);
            $Statement->bindParam(':creation_time', $TimeNow);
            $Statement->bindParam(':session_data', $Data);
            $Statement->bindParam(':session_key', $Key);
            $Statement->execute();
            return true;
        }
    
        static function Destroy($SessionID)
        {
            $Statement = Session::$DBConnection->prepare('DELETE FROM sessions WHERE id = :sessionid');
            $Statement->bindParam(':sessionid', $SessionID);
            $Statement->execute();
            Session::$SessionCreated = false;
            return true;
        }
    
        private static function GarbageCollector($Max)
        {
            $Statement = Session::$DBConnection->prepare('DELETE FROM sessions WHERE set_time < :maxtime');
            $OldSessions = time()-$Max;
            $Statement->bindParam(':maxtime', $OldSessions);
            $Statement->execute();
            return true;
        }
    
        private static function GetKey($SessionID)
        {
            $Statement = Session::$DBConnection->prepare('SELECT session_key FROM sessions WHERE id = :sessionid LIMIT 1');
            $Statement->bindParam(':sessionid', $SessionID);
            $Statement->execute();
            $Result = $Statement->fetch(PDO::FETCH_ASSOC);
            if($Result['session_key'] != '')
                return $Result['session_key'];
            else
                return hash('sha512', uniqid(mt_rand(1, mt_getrandmax()), true));
        }
    
        private static function Encrypt($SessionData, $SessionKey)
        {
            $Salt = "06wirrdzHDvc*t*nJn9VWIfET+|co*pm~CbtT5P*S2IPD-VmEfd+CX2wrvZ";
            $SessionKey = substr(hash('sha256', $Salt.$SessionKey.$Salt), 0, 32);
            $Get_IV_Size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
            $IV = mcrypt_create_iv($Get_IV_Size, MCRYPT_RAND);
            $Encrypted = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $SessionKey, $SessionData, MCRYPT_MODE_ECB, $IV));
            return $Encrypted;
        }
    
        private static function Decrypt($SessionData, $SessionKey)
        {
            $Salt = "06wirrdzHDvc*t*nJn9VWIfET+|co*pm~CbtT5P*S2IPD-VmEfd+CX2wrvZ";
            $SessionKey = substr(hash('sha256', $Salt.$SessionKey.$Salt), 0, 32);
            $Get_IV_Size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
            $IV = mcrypt_create_iv($Get_IV_Size, MCRYPT_RAND);
            $Decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $SessionKey, base64_decode($SessionData), MCRYPT_MODE_ECB, $IV);
            return $Decrypted;
        }
    }
    
    ?>
    

    我不能排除私有静态函数(提到的第一个),从那以后我就无法设置变量了。

    你可能会说:'但是有一个UpdateSession方法'...... 是的......有点......但事实是,由于我猜我的知识不足,我搞砸了某个地方的剧本,逻辑出错了。

    以下是链接(可能会简化理解):
    Sessions.FreedomCore.php - 会议班
    String.FreedomCore.php - Captcha Generation(指向精确线)
    pager.php - 帐户创建流程(仅适用于session_regenerate_id)
    pager.php - 验证码显示过程(在某些情况下始终有效)
    pager.php - 执行登录案例(出于某种原因无任何问题)

    如果你对这实际上如何工作感兴趣(我的意思是它在4次刷新后取消对用户的授权)
    Please head over here
    用户名:测试
    密码: 123456

    所以问题是: 如何修改我的类,使用当前方法保存session_regenerate_id(true)的会话数据,并防止在调用session_regenerate_id后刷新它。

    这些链接直接指向脚本中有问题的区域 任何帮助都非常感谢。

    非常感谢您的帮助!

1 个答案:

答案 0 :(得分:1)

您正在体验我所谓的 Cookie竞争条件

当您使用session_regenerate_id(true)时,PHP会创建一个新的会话ID(包含旧会话的数据),并从每个请求的数据库中删除旧会话。

现在,您的网站包含许多需要加载的元素,例如/pager.php/data/menu.json。每次为浏览器分配一个新的会话ID 。通常不是问题,但现代浏览器并行提出请求:

    {li> pager.php请求session_id = a {li> data/menu.json请求session_id = a
  1. pager.php删除sessions_id = a并将session_id = b返回给我的浏览器。
  2. data/menu.json在数据库中找不到session_id = a,并假设我是新访问者并向我提供了session_id = c
  3. 现在它取决于浏览器以哪种顺序接收和解析哪个请求。

    首先解析

    案例A data/menu.json:浏览器存储session_id = c。然后解析pager.php的响应,浏览器用 b 覆盖session_id。对于任何下一个请求,它将使用session_id = b

    首先解析

    案例B pager.php,然后解析data/menu.json。浏览器现在存储session_id = c,您已退出

    这解释了为什么它有时会工作(例如,4或6次刷新),有时不会。

    结论:没有充分理由不使用session_regenerate_id();

    请提出一个新问题,为什么验证码创建机制在注册页面上不起作用,但在登录页面上。

    有关加密的一些注意事项。

    1. 使用AES和ECB模式。这削弱了加密。
    2. 您将加密密钥存储在数据旁边。你的加密爆炸了。