会话丢失,除非我使用"不安全"饼干

时间:2016-11-01 09:20:54

标签: php security session session-cookies

前言

这些答案根本没有帮助我,但他们可能会帮助那些偶然发现这个问题的人:

问题

我昨天整天都在打破我的花生,而且在登录我自己的应用程序时,我似乎无法幸免。关于SO的所有有用的答案都是"你开始会话了吗?" - 我有。

当我登录时,脚本返回状态正确并给我以下标题:

"Expires: Thu, 19 Nov 1981 08:52:00 GMT",
"Cache-Control: no-store, no-cache, must-revalidate",
"Pragma: no-cache",
"Set-Cookie: ADMSESSION=f27n49icvbv0bjfgh4bffojgs0; path=\/; secure; HttpOnly"

我可以验证此cookie确实存在于我的浏览器中,并且仅在浏览器关闭时才终止cookie。

现在这就是魔术不会发生的地方。从服务器接收到前进状态后,本地脚本会刷新页面以加载真正的后端会话。至少,这是它应该做的事情。该脚本重新加载页面,我再次受到相同的登录提示。它把我推到了墙上。

仔细检查后,我发现我的会话cookie根本没有被使用,所以我把它改成了一个不安全的cookie(Set-Cookie: ADMSESSION=f27n49icvbv0bjfgh4bffojgs0)。此似乎适用于要求用户登录的所有页面...但是,它仅适用于这些页面,而不适用于登录页面。我无法绕过这个,因为它在逻辑中遵循相同的轨迹,并且在服务器完全完成处理之前不会发送任何标题。

相关摘要

以下是一些相关的片段,说明了逻辑流程以及设置和获取会话的方式。出于安全目的,已对某些信息进行了编辑。

<?php // index.php

    /**
     * just print everything, I want to know when, where, and how
     * the brown sticky stuff starts hitting the ceiling whirly device.
     */
    error_reporting(E_ALL);
    ini_set("display_startup_errors", 1);
    ini_set("display_errors", 1);

    require_once "path/to/my/config.php";

    // session and login manager
    require_once "path/to/my/SecurityService.php";

    use \Namespace\Of\My\SecurityService as LoginManager;

    // argument false: don't log activity on this page.
    $loginManager = new LoginManager(false);

    // start session
    $loginManager->secureSessionStart();

    // @fixme: secureSessionCheck is not fired!?
    if($loginManager->secureSessionCheck()) {
        print file_get_contents("path/to/views/main.html");
    } else {
        print file_get_contents("path/to/views/login.html");
    }
<?php // SecurityService.php:51-75 (inside login method called via POST)
        if($this->verify($credentials["passwordHash"])) {
            if($this->logger !== null)
                $this->logger->write("info", "successful login from {$_SERVER["REMOTE_ADDR"]}.");

            $_SESSION["browser"] = $_SERVER["HTTP_USER_AGENT"];
            // redacted
            // redacted
            $_SESSION["visitor"] = $_SERVER["REMOTE_ADDR"];
            // redacted
            // redacted
            // redacted
            // redacted
            // redacted
            // redacted
            // redacted
            // redacted
            // redacted

            session_commit();

            if($this->logger !== null)
                $this->logger->write("info", "User ({$_SESSION["email"]}) created in session (" . session_id() . ").");

            return true;
        }
<?php // SecurityService.php:92-127
    /**
     * secureSessionStart
     *   Starts a session in a secure way
     */
    public function secureSessionStart() {
        if(ini_set("session.use_only_cookies", 1) === FALSE) {
            if($this->logger !== null)
                $this->logger->write("error", "PHP ini is configured incorrectly. (session.use_only_cookies)");

            return;
        }

        switch(session_status()) {
            case PHP_SESSION_DISABLED:
                if($this->logger !== null)
                    $this->logger->write("error", "Sessions are disabled. Unable to log this user in!");
                return;
            case PHP_SESSION_NONE:
                session_set_cookie_params(0, "/", "", true, true);
                session_name("ADMSESSION");
                session_start();
                break;
            case PHP_SESSION_ACTIVE:
                session_set_cookie_params(0, "/", "", true, true);
                session_name("ADMSESSION");
                session_start();

                $oldID = session_id();

                session_regenerate_id(true);

                if($this->logger !== null)
                    $this->logger->write("info", "Session ({$oldID}) moved to (" . session_id() . ").");
                break;
        }
    }
<?php // SecurityService.php:118-189
    /**
     * secureSessionCheck
     *   Checks the current session if it"s valid with current data
     *
     * @return boolean
     */
    public function secureSessionCheck() {
        if(!isset($_SESSION)) {
            if($this->logger !== null)
                $this->logger->write("error", "Session is not set.");

            return false;
        }

        if(!isset($_SESSION[/* redacted */]) || !isset($_SESSION["browser"]) || !isset($_SESSION["visitor"])) {
            if($this->logger !== null)
                $this->logger->write("error", "Session (" . session_id() . ") does not contain a valid administrator.");

            return false;
        }

        if($_SESSION["browser"] !== $_SERVER["HTTP_USER_AGENT"]) {
            if($this->logger !== null)
                $this->logger->write("warning", "Session (" . session_id() . ") browser conflicts with current user agent.");

            return false;
        }

        if($_SESSION["visitor"] !== $_SERVER["REMOTE_ADDR"]) {
            if($this->logger !== null)
                $this->logger->write("warning", "Session (" . session_id() . ") visitor conflicts with current visitor IP.");

            return false;
        }

        // redacted (gets $this->data)

        if(!isset($this->data)) {
            if($this->logger !== null)
                $this->logger->write("error", "Data object is not set.");

            return false;
        }

        if(!isset($this->data->/* redacted */) || !isset($this->data->/* redacted */) || !isset($this->data->/* redacted */) || !isset($this->data->/* redacted */)) {
            if($this->logger !== null)
                $this->logger->write("error", "Data object does not contain a valid administrator.");

            return false;
        }

        $compare = // redacted
            // redacted
            // redacted
            // redacted
            // redacted
            // redacted
            // redacted
            // redacted
        // redacted

        $isValid = hash_equals($compare, $_SESSION[/* redacted */]);

        if(!$isValid) {
            if($this->logger !== null)
                $this->logger->write("error", "Illegal session presented by {$_SERVER["REMOTE_ADDR"]}.");

            return false;
        }

        return true;
    }

我的日志片段(登录后一次)

2016-11-01 09:18:55 – [info] Session (359hk6v83pc3a82b9tlbn79ch5) moved to (6g05qcg7pocaht66ru5gdq8di6).
2016-11-01 09:18:55 – [info] successful login from 127.0.0.1.
2016-11-01 09:18:55 – [info] User (mail@cytodev.io) created in session (6g05qcg7pocaht66ru5gdq8di6).
2016-11-01 09:19:08 – [info] Session (6g05qcg7pocaht66ru5gdq8di6) moved to (jo0etnds61ip2ices59hrg1jl6).  

最后一个片段中的奇怪之处在于,在将会话移动到新ID后,它不会记录任何内容。我希望(在我尝试访问未登录的页面的情况下)我的日志中至少包含以下内容:2016-11-01 10:08:47 – [error] Session (jo0etnds61ip2ices59hrg1jl6) does not contain a valid administrator. - 登录页面上的奇怪行为......

问题与解答

问:您是否检查过浏览器控制台?那里有与HTTPS有关的任何警告?也许混合内容警告或类似的东西? - CBroe
A:无论如何。登录页面加载没有任何问题,将数据发送到服务器,服务器响应正确。无法找到与HTTPS相关的控制台消息。网络选项卡也不显示200以外的任何状态。登录一次(通过POST请求),然后页面刷新。我可以像这样跟踪会话cookie:1(初始加载) - &gt;没有发送,1收到(071s21bdejg40supml9kamced5)。 2(POST登录) - &gt;没有发送,1收到(813um7v1jjh3jsi57g5k8evm16) - 这不应该是一个问题,对吧?由于用户应该登录到新的会话ID。 3(重新加载) - &gt; 1发送(071s21bdejg40supml9kamced5),1收到(k5aa0o4k24v2cfc41qbleq04h4)......

通过检查发送/接收的cookie找到了实际问题。解决这个问题的方法没有。

目前对问题的理解

根据我在阅读CBroe评论后所采取的步骤找到了这个问题。

我似乎从服务器回来了错误的会话cookie,我相信我可以将其归因于secureSessionStart函数。我删除了cookie的设置(用于测试目的)并且我已正确登录。但是,这会破坏使用无法通过脚本访问的cookie的安全会话的整个目的。当我只使用session_start()时,一切都很好,我受到应用程序实际后端的欢迎。每当我使用session_set_cookie_params(0, "/", "", true, true)时,登录页面会一次又一次地弹出。

最后的想法

它可能在某处丢失了分号。它总是缺少分号......

1 个答案:

答案 0 :(得分:1)

显然,我的服务器没有收到安全标头,这导致我的登录会话在重新加载时丢失。我目前通过在设置会话之前检查连接是否安全来解决此问题。

声明

不要建议使用不安全的连接来处理您的登录请求!如果你遇到了我一直遇到的同样的问题,请务必检查连接是否一直都是安全的,然后再在会话cookie中关闭它!

当前修复

secureSessionStart方法进行了两处小的添加,以便为会话cookie添加域名和安全性。请始终确保连接 安全。这应仅用于测试目的!

/**
 * get's the secure status from _SERVER
 *   Also check against HTTP_X_FORWARDED and HTTP_X_FORWARDED_SSL because
 *   some servers are behind load balancers.
 */
$secure = (isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] === "on") && ((!empty($_SERVER["HTTP_X_FORWARDED_PROTO"]) && $_SERVER["HTTP_X_FORWARDED_PROTO"] === "https") || (!empty($_SERVER["HTTP_X_FORWARDED_SSL"]) && $_SERVER["HTTP_X_FORWARDED_SSL"] === "on"));

// also use _SERVER["SERVER_NAME"] to set the cookie to this domain only
session_set_cookie_params(0, "/", $_SERVER["SERVER_NAME"], $secure, true);  

理想的修复

理想的解决方法是在需要它的每个页面上使用SSL。我正在使用的当前域没有这个设置,我正在修复它,因为我正在输入它。我甚至不开玩笑。在构建登录系统之前获取SSL。