PHP Sessions and session_regenerate_id


PHP Sessions and session_regenerate_id

我已经尝试解决这个问题好几个星期了。
我已经做了一个类来保护PHP会话,它工作得很好,除非有人试图执行注册(这是问题#2),如果一些功能被禁用(这是导致#2发生),网站的其余部分工作得很好。

那么问题来了:

  1. session_regenerate_id -注释掉
    从这里开始,一切都工作得很好,除了验证码创建机制(仅在注册页面上),对于登录页面它工作得很好
  2. session_regenerate_id(true) -未注释
    在这里,注册工作得很好,没有问题,没有captcha问题,但几次刷新页面后,会话就消失了,所以用户需要登录6次刷新和$_SESSION再次设置为null

我知道问题出在哪里,但我不知道如何解决。

我有一个私有静态函数,它在之后被调用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 - Sessions类
字符串. freedomcore .php -验证码生成(指向准确的行)
page .php -帐户创建过程(仅适用于session_regenerate_id)
pager.php -验证码显示过程(在某些情况下总是有效)
page .php -执行登录案例(没有任何问题在任何情况下的某些原因)

如果你对它是如何工作的非常感兴趣(我的意思是它是如何在4次刷新后取消用户授权的)
请转到这里
用户名:测试
密码: 123456

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

这些链接直接指向脚本中有问题的地方。
非常感谢您的帮助。

非常感谢您的帮助!

您正在经历我所说的Cookie竞态

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

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

    session_id = a 请求pager.php
  1. data/menu.json请求session_id = a
  2. pager.php丢弃sessions_id = a并返回session_id = b到我的浏览器。
  3. data/menu.json无法在数据库中找到session_id = a,并假设我是新访客,并给我session_id = c

现在它取决于浏览器以什么顺序接收和解析哪个请求。

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

首先解析

Case B pager.php,然后解析data/menu.json。浏览器现在存储session_id = c,您已注销

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

结论:没有很好的理由不要使用session_regenerate_id(); !


请提出一个新的问题,为什么captcha创建机制不工作在注册页面,但在登录页面。


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

  1. Do NOT使用AES with ECB Mode。这将削弱加密。
  2. 您将加密密钥存储在数据旁边。你的加密系统刚刚崩溃了。