簡體   English   中英

PHP會話和session_regenerate_id

[英]PHP Sessions and session_regenerate_id

我已經嘗試解決這個問題很多周了。
我已經建立了一個類來保護PHP會話,並且可以正常工作,除非有人嘗試執行注冊(問題2),並且如果某些功能被禁用(導致問題2發生),其余的網站工作正常。

所以這是問題所在:

  1. session_regenerate_id-已注釋掉
    從這里開始,除了驗證碼創建機制(僅在注冊頁面上)以外,其他一切都正常,對於登錄頁面來說一切正常
  2. session_regenerate_id(true)-未注釋
    在這里,注冊工作正常,沒有問題,沒有驗證碼問題,但是在刷新頁面后,會話就消失了,因此用戶需要登錄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-會話類
String.FreedomCore.php-驗證碼生成(指向確切的行)
pager.php-帳戶創建過程(僅適用於session_regenerate_id)
pager.php-驗證碼顯示過程(在某些情況下始終有效)
pager.php-執行登錄案例(由於某種原因無論如何都沒有問題)

如果您對它的實際工作方式超級感興趣(我的意思是4次刷新后它如何取消對用戶的授權)
請到這里
用戶名: test
密碼: 123456

因此,問題是:如何修改我的類,如何使用當前方法使用session_regenerate_id(true)保存會話數據,並防止在調用session_regenerate_id之后刷新它。

這些鏈接直接指向腳本中有問題的區域。
任何幫助都非常感謝。

非常感謝您的幫助!

您正在體驗所謂的Cookie競賽條件

當您使用session_regenerate_id(true) PHP將創建一個新的會話ID(包含舊會話的數據),並針對每個請求從數據庫中刪除該舊會話。

現在,您的網站包含許多需要加載的元素,例如/pager.php/data/menu.json 每次給瀏覽器分配一個新的會話ID 通常這不是問題,但是現代瀏覽器會並行執行請求:

  1. 使用session_id = a請求pager.php
  2. 使用session_id = a請求data/menu.json
  3. pager.php刪除sessions_id = a session_id = b並向我的瀏覽器返回session_id = b
  4. data/menu.json在數據庫中找不到session_id = a並假設我是新訪客,並給我session_id = c

現在,它取決於瀏覽器以哪個順序接收和解析哪個請求。

案例A data/menu.json被解析:瀏覽器存儲session_id = c 然后解析pager.php的響應,瀏覽pager.php b覆蓋session_id。 對於任何下一個請求,它將使用session_id = b

案例B pager.php解析pager.php ,然后解析data/menu.json 瀏覽器現在存儲session_id = c並且您已注銷

這解釋了為什么它有時可以工作(例如4或6次刷新),而有時卻沒有。

結論:不要使用session_regenerate_id(); 沒有很好的理由!


請提出一個新的問題,為什么驗證碼創建機制在注冊頁面上不起作用,而在登錄頁面上不起作用。


有關加密的一些注意事項。

  1. 不要用ECB模式下使用AES。 這會削弱加密。
  2. 您將加密密鑰存儲在數據旁邊。 您的加密就爆了。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM