Вопрос:

session_regenerate_id вызывает возврат двух файлов cookie PHPSESSID

php cookies

62 просмотра

1 ответ

14780 Репутация автора

Я разработал API, который первоначально использовался только через браузер, но никогда не замечал проблемы, однако сейчас я пытаюсь подключиться к нему через стороннюю библиотеку Android (OkHttpClient), и я протестировал то, что вижу, используя REST API тестовый клиент (Insomnia.rest).

Проблема, с которой я сталкиваюсь, заключается в том, что, когда я выполняю действие входа API, я запускаю сеанс и вызываю session_regenerate_id(true); чтобы избежать атак липкой сессии (я не уверен, что это правильное имя).

Однако, когда я делаю это, я возвращаю два файла cookie PHPSESSID, как показано в заголовках ниже:

< HTTP/1.1 200 OK
< Date: Thu, 18 Apr 2019 22:51:43 GMT
< Server: Apache/2.4.27 (Win64) PHP/7.1.9
< X-Powered-By: PHP/7.1.9
* cookie size: name/val 8 + 6 bytes
* cookie size: name/val 4 + 1 bytes
< Set-Cookie: ClientID=413059; path=/
* cookie size: name/val 9 + 26 bytes
* cookie size: name/val 4 + 1 bytes
< Set-Cookie: PHPSESSID=15u9j1p2oinfl5a8slh518ee9r; path=/
< Expires: Thu, 19 Nov 1981 08:52:00 GMT
< Cache-Control: no-store, no-cache, must-revalidate
< Pragma: no-cache
* cookie size: name/val 9 + 26 bytes
* cookie size: name/val 4 + 1 bytes
* Replaced cookie PHPSESSID="hkkffpj8ta9onsn92pp70r257v" for domain localhost, path /, expire 0
< Set-Cookie: PHPSESSID=hkkffpj8ta9onsn92pp70r257v; path=/
* cookie size: name/val 17 + 1 bytes
* cookie size: name/val 4 + 1 bytes
< Set-Cookie: UsingGoogleSignIn=0; path=/
* cookie size: name/val 6 + 1 bytes
* cookie size: name/val 4 + 1 bytes
< Set-Cookie: UserID=7; path=/
< Access-Control-Allow-Credentials: true
< Content-Length: 47
< Content-Type: application/json

Как видно из вышеприведенного вывода, есть два Set-Cookies с PHPSESSID. Если я удаляю session_regenerate_id, я получаю только один файл cookie PHPSESSID, после чего клиент Android успешно работает.

Я показал это на Apache под Wamp на Windows 10 и Apache в рабочей сборке CentOS 7.

Итак, вопрос в том, как я могу сгенерировать новый идентификатор сеанса PHP без отправки двух разных файлов cookie PHPSESSID?

UDPATE

Ниже приведен код, относящийся к процессу входа в систему. Я не могу включить весь код, но он должен показать концепцию того, что происходит.

Запрос API сделан для функции входа в систему

$email = mysqli_escape_string($this->getDBConn(), $encryption->encrypt($postArray["email"]));
        $password = mysqli_escape_string($this->getDBConn(), $encryption->encrypt($postArray["password"]));
        $externalDevice = isset($postArray["external_device"]) ? abs($postArray["external_device"]) : 0;

        $query = "SELECT * FROM users WHERE Email='$email'";
        $result = $this->getDBConn()->query($query);
        if ($result)
        {
            if (mysqli_num_rows($result) > 0 )
            {
                $myrow = $result->fetch_array();
                if ($myrow["UsingGoogleSignIn"] === '1')
                {
                    //We're trying to login as a normal user, but the account was registered using Google Sign In
                    //so tell the user to login via google instead
                    return new APIResponse(API_RESULT::SUCCESS, "AccountSigninViaGoogle");
                }
                else
                {
                    //Check the password matches
                    if ($myrow["Password"] === $password)
                    {
                        $this->getLogger()->writeToLog("Organisation ID: " . $myrow["Organisation"]);
                        $organisationDetails = $this->getOrganisationDetails(abs($myrow["Organisation"]), false);
                        $this->getLogger()->writeToLog(print_r($organisationDetails, true));

                        $this->createLoginSession($myrow, $organisationDetails, false, $paymentRequired, $passwordChangeRequired);
                        $data = null;
                        if ($externalDevice === 1)
                        {
                            $data = new stdClass();
                            $data->AuthToken = $_SESSION["AuthToken"];
                            $data->ClientID = $_SESSION["ClientID"];
                            $data->UserID = abs($_SESSION["UserID"]);
                        }

                        $this->getLogger()->writeToLog("Login Response Headers");
                        $headers = apache_response_headers();
                        $this->getLogger()->writeToLog(print_r($headers, true));

В этот момент возвращается ответ API, который содержит объект JSON

В приведенном выше коде, если адрес электронной почты и пароль совпадают (без использования Google, войдите здесь), он вызывает createLoginSession:

private function createLoginSession($myrow, $organisationDetails, $usingGoogleSignIn, &$paymentRequired, &$passwordChangeRequired)
    {
        require_once 'CommonTasks.php';
        require_once 'IPLookup.php';
        require_once 'Encryption.php';
        try
        {
            $this->getLogger()->writeToLog("Creating login session");
            $paymentRequired = false;
            if ($organisationDetails === null)
            {
                $organisationDetails = $this->getOrganisationDetails($myrow["Organisation"]);
            }
            $encryption = new Encryption();
            $userID = mysqli_escape_string($this->getDBConn(), $myrow["UserID"]);
            $organisationID = intval(abs($myrow["Organisation"]));

            $commonTasks = new CommonTasks();
            $browserDetails = $commonTasks->getBrowserName();
            $this->getLogger()->writeToLog("Browser Details");
            $this->getLogger()->writeToLog(print_r($browserDetails, true));
            $clientName = $browserDetails["name"];

            $iplookup = new IPLookup(null, $this->getLogger());
            $ipDetails = json_decode($iplookup->getAllIPDetails($commonTasks->getIP()));

            if ($ipDetails !== null)
            {
                $ip = $ipDetails->ip;
                $country = $ipDetails->country_name;
                $city = $ipDetails->city;
            }
            else
            {
                $ip = "";
                $country = "";
                $city = "";
            }

            //Create a random client ID and store this as a cookie
            if (isset($_COOKIE["ClientID"]))
            {
                $clientID = $_COOKIE["ClientID"];
            }
            else
            {
                $clientID = $commonTasks->generateRandomString(6, "0123456789");
                setcookie("ClientID", $clientID, 0, "/");
            }

            //Create an auth token
            $authToken = $commonTasks->generateRandomString(25);
            $encryptedAuthToken = $encryption->encrypt($authToken);

            $query = "REPLACE INTO client (ClientID, UserID, AuthToken, ClientName, Country, City, IPAddress) " .
                "VALUES ('$clientID', '$userID', '$encryptedAuthToken', '$clientName', '$country', '$city', '$ip')";
            $result = $this->getDBConn()->query($query);
            if ($result)
            {

                session_start();
                $this->getLogger()->writeToLog("Logging in and regnerating session id");
                session_regenerate_id(true);

                $_SESSION["AuthToken"] = $authToken;
                $_SESSION["ClientID"] = $clientID;
                $_SESSION["UserID"] = $userID;
                $_SESSION["FirstName"] = $this->getEncryption()->decrypt($myrow["FirstName"]);
                $_SESSION["LastName"] = $this->getEncryption()->decrypt($myrow["LastName"]);

                $passwordChangeRequired = $myrow["PasswordChangeRequired"] === "1" ? true : false;
                //Check if the last payment failure reason is set, if so, set a cookie with the message but only
                //if the organisation is not on the free plan
                //Logger::log("Current Plan: " . $this->getOrganisationDetails(->getPlan()));
                if ($organisationDetails->getPlan() !== "Free")
                {
                    if (!empty($organisationDetails->getLastPaymentFailureReason()))
                    {
                        $this->getLogger()->writeToLog("Detected last payment as a failure. Setting cookies for organisation id: " . $organisationDetails->getId());
                        setcookie("HavePaymentFailure", true, 0, "/");
                        setcookie("PaymentFailureReason", $organisationDetails->getLastPaymentFailureReason(), 0, "/");

                    }
                    //Check if the current SubscriptionPeriodEnd is in the past
                    $subscriptionPeriodEnd = $organisationDetails->getSubscriptionOfPeriod();
                    $currentTime = DateTimeManager::getEpochFromCurrentTime();
                    if ($currentTime > $subscriptionPeriodEnd)
                    {
                        $this->getLogger()->writeToLog("Detected payment overdue for organisation: " . $organisationDetails->getId());
                        //The payment was overdue, determine the number of days grace period (there's a 7 day grace period) that's left
                        $subscriptionPeriodEndGracePeriod = $subscriptionPeriodEnd + (86400 * 7);
                        $numberOfDaysRemaining = floor((($subscriptionPeriodEndGracePeriod - $currentTime) / 86400));

                        setcookie("PaymentOverdue", true, 0, "/");
                        setcookie("DaysGraceRemaining", $numberOfDaysRemaining, 0, "/");
                        if ($numberOfDaysRemaining <= 0)
                        {
                            $paymentRequired = true;
                        }
                    }
                }
                setcookie("UsingGoogleSignIn", $usingGoogleSignIn ? "1" : "0", 0, "/");
                if ($organisationDetails->getId() !== 0)
                {

                    $_SESSION["OrganisationDetails"] = array();
                    $_SESSION["OrganisationDetails"]["id"] = $organisationDetails->getId();
                    $_SESSION["OrganisationDetails"]["Name"] = $organisationDetails->getName();

                }
                setcookie("UserID", $userID, 0, "/");
                $this->getLogger()->writeToLog("Successfully created login session. User ID '$userID' and Organisation ID '$organisationID'");
                return true;
            }
            else
            {
                $error = mysqli_error($this->getDBConn());
                $this->getLogger()->writeToLog("Failed to create login session. DB Error: $error");
                $this->getAlarms()->setAlarm(AlarmLevel::CRITICAL, "AccountManagement", "Failed to create login session. DB Error");
                throw new DBException($error);
            }
        }
        catch (DBException $ex)
        {
            throw $ex;
        }
    }

В функции выше я называю , session_start()а затем regenerate_session_id()и после этого я получаю два PHPSESSID печенья в ответ , хотя строка лог только выводится один раз , так его определенно не вызывалось несколько раз.

Если я удалю, regenerate_session_idто проблема исчезнет. Чтобы быть в безопасности, я попытался поменять местами, session_start()так что это происходит после, regenerate_session_idно похоже, что идентификатор сеанса не создается заново, как ожидалось.

Автор: Boardy Источник Размещён: 18.04.2019 10:54

Ответы (1)


0 плюса

580 Репутация автора

Этот заголовок фактически удалит cookie. Это единственный способ истечения срока действия cookie только для HTTP: сделать так, чтобы срок действия истек задним числом.

Set-Cookie: PHPSESSID=15u9j1p2oinfl5a8slh518ee9r; path=/
< Expires: Thu, 19 Nov 1981 08:52:00 GMT
Автор: Anatoliy Gusarov Размещён: 25.04.2019 09:55
32x32