“保持登录状态”-最佳方法


257

我的Web应用程序使用会话来存储有关用户登录后的信息,并在用户在应用程序中逐页浏览时维护该信息。在这个特定的应用程序,我存储user_idfirst_namelast_name人的。

我想在登录时提供一个“保持登录状态”选项,该选项将cookie放置在用户计算机上两周,当他们返回应用程序时,将使用相同的详细信息重新开始会话。

这样做的最佳方法是什么?我不想将其存储user_id在cookie中,因为这似乎会使一个用户易于尝试伪造另一位用户的身份。

Answers:


735

好的,让我直言不讳:如果您为此目的将用户数据或从用户数据派生的任何内容放入cookie,则表示您做错了。

那里。我说了。现在我们可以继续实际的答案了。

您问对用户数据进行散列有什么问题?好吧,它可归结为暴露表面和安全性。

想象一下您是攻击者。您会在会话中看到为“记住我”设置的加密cookie。宽度为32个字符。啧啧。那可能是MD5 ...

我们还要想象一下,他们知道您使用的算法。例如:

md5(salt+username+ip+salt)

现在,攻击者所需要做的就是强行使用“盐”(这实际上不是盐,但稍后会更多),并且他现在可以使用其IP地址的任何用户名生成他想要的所有伪造令牌!但是强行撒盐很难,对吗?绝对。但是现代GPU非常擅长于此。而且,除非您在其中使用足够的随机性(使其足够大),否则它将很快下降,并随即成为城堡的关键。

简而言之,唯一保护您的是盐,它实际上并没有像您想象的那样保护您。

可是等等!

所有这些都假定攻击者知道该算法!如果它是秘密和令人困惑的,那么您很安全,对吗?错误的。这种思路有一个名字:“ 通过模糊实现安全”永远不要依赖它。

更好的方法

更好的方法是永远不要让用户的信息离开服务器,除了ID。

用户登录时,生成一个较大的(128至256位)随机令牌。将其添加到将令牌映射到用户ID的数据库表中,然后将其发送到Cookie中的客户端。

如果攻击者猜测另一个用户的随机令牌怎么办?

好吧,让我们在这里做一些数学运算。我们正在生成一个128位随机令牌。这意味着有:

possibilities = 2^128
possibilities = 3.4 * 10^38

现在,为了说明这个数字有多么荒谬,让我们想象一下互联网上的每台服务器(今天的数字是5000万)试图以每秒10亿的速度强行暴力破解该数字。实际上,您的服务器会在这样的负载下融化,但让我们来解决一下。

guesses_per_second = servers * guesses
guesses_per_second = 50,000,000 * 1,000,000,000
guesses_per_second = 50,000,000,000,000,000

因此,每秒50万亿次猜测。快!对?

time_to_guess = possibilities / guesses_per_second
time_to_guess = 3.4e38 / 50,000,000,000,000,000
time_to_guess = 6,800,000,000,000,000,000,000

6.8秒

让我们尝试将其简化为更友好的数字。

215,626,585,489,599 years

甚至更好:

47917 times the age of the universe

是的,这是宇宙年龄的47917倍...

基本上,它不会被破解。

总结一下:

我建议的更好的方法是将cookie分为三个部分存储。

function onLogin($user) {
    $token = GenerateRandomToken(); // generate a token, should be 128 - 256 bit
    storeTokenForUser($user, $token);
    $cookie = $user . ':' . $token;
    $mac = hash_hmac('sha256', $cookie, SECRET_KEY);
    $cookie .= ':' . $mac;
    setcookie('rememberme', $cookie);
}

然后,进行验证:

function rememberMe() {
    $cookie = isset($_COOKIE['rememberme']) ? $_COOKIE['rememberme'] : '';
    if ($cookie) {
        list ($user, $token, $mac) = explode(':', $cookie);
        if (!hash_equals(hash_hmac('sha256', $user . ':' . $token, SECRET_KEY), $mac)) {
            return false;
        }
        $usertoken = fetchTokenByUserName($user);
        if (hash_equals($usertoken, $token)) {
            logUserIn($user);
        }
    }
}

注意:请勿使用令牌或用户和令牌的组合在数据库中查找记录。始终确保根据用户来获取记录,并使用时序安全比较功能随后对获取的令牌进行比较。有关定时攻击的更多信息

现在,非常重要的一点是,它SECRET_KEY必须是加密的秘密(由类似东西的东西生成/dev/urandom和/或从高熵输入中得出)。此外,还GenerateRandomToken()需要成为一个强大的随机源(mt_rand()不够强大。请使用一个库,例如RandomLibrandom_compat,或mcrypt_create_iv()与一起使用DEV_URANDOM)...

hash_equals()是为了防止定时攻击。如果您使用PHP 5.6以下的PHP版本,hash_equals()则不支持该功能。在这种情况下,您可以替换hash_equals()为timingSafeCompare函数:

/**
 * A timing safe equals comparison
 *
 * To prevent leaking length information, it is important
 * that user input is always used as the second parameter.
 *
 * @param string $safe The internal (safe) value to be checked
 * @param string $user The user submitted (unsafe) value
 *
 * @return boolean True if the two strings are identical.
 */
function timingSafeCompare($safe, $user) {
    if (function_exists('hash_equals')) {
        return hash_equals($safe, $user); // PHP 5.6
    }
    // Prevent issues if string length is 0
    $safe .= chr(0);
    $user .= chr(0);

    // mbstring.func_overload can make strlen() return invalid numbers
    // when operating on raw binary strings; force an 8bit charset here:
    if (function_exists('mb_strlen')) {
        $safeLen = mb_strlen($safe, '8bit');
        $userLen = mb_strlen($user, '8bit');
    } else {
        $safeLen = strlen($safe);
        $userLen = strlen($user);
    }

    // Set the result to the difference between the lengths
    $result = $safeLen - $userLen;

    // Note that we ALWAYS iterate over the user-supplied length
    // This is to prevent leaking length information
    for ($i = 0; $i < $userLen; $i++) {
        // Using % here is a trick to prevent notices
        // It's safe, since if the lengths are different
        // $result is already non-0
        $result |= (ord($safe[$i % $safeLen]) ^ ord($user[$i]));
    }

    // They are only identical strings if $result is exactly 0...
    return $result === 0;
}

7
但是,这种方法难道不是任何人都可以使用此用户名和Cookie并以其他用户身份登录此用户吗?
更简单的2013年

8
大声笑:-),请注意,最长猜测时间为47917年,也可以在1小时内猜测随机令牌。
storm_buster

32
这很奇怪,因为您的代码与您的答案相矛盾。您说“如果将用户数据放入cookie中,则说明您在做错事”,但这正是您的代码正在做的事!从cookie中删除用户名,仅计算令牌上的哈希值(并可能添加IP地址以防止cookie盗窃),然后执行fetchUsernameByToken而不是RememberMe()中的fetchTokenByUserName更好吗?
2014年

9
从PHP 5.6开始,在进行字符串比较时,可以使用hash_equals防止计时攻击。
F21 2014年

5
@Levit它可防止某人获取有效令牌并更改附加到该令牌的用户标识。
ircmaxell,2014年

93

安全公告:将cookie置于确定性数据的MD5哈希基础上是一个坏主意;最好使用从CSPRNG派生的随机令牌。有关更安全的方法,请参阅ircmaxell对这个问题的回答

通常我会这样:

  1. 用户以“保持登录状态”登录
  2. 建立工作阶段
  3. 创建一个名为SOMETHING的cookie,其中包含:md5(salt + username + ip + salt)和一个名为somethingElse的cookie,其中包含id
  4. 将Cookie存储在数据库中
  5. 用户做东西离开----
  6. 用户返回,检查是否有其他cookie(如果存在),从该用户的数据库中获取旧哈希,检查cookie内容是否与数据库中的哈希匹配,后者也应与新计算的哈希匹配(对于ip)这样:cookieHash == databaseHash == md5(salt + username + ip + salt),如果是,转到2,如果不转到1

当然,您可以使用其他Cookie名称等。也可以稍微更改Cookie的内容,只需确保不要轻易创建它即可。例如,您还可以在创建用户时创建一个user_salt,并将其放入cookie中。

您也可以使用sha1代替md5(或几乎所有算法)


30
为什么在哈希中包含IP?另外,请确保在cookie中包括时间戳记信息,并使用此信息来确定cookie的最长使用期限,以便您不会创建对永恒有利的身份令牌。
Scott Mitchell 2010年

4
@Abhishek Dilliwal:这是一个很老的话题,但是我碰到它在寻找与Mathew相同的答案。我认为使用session_ID不能解决Pim的问题,因为您无法检查db哈希,cookie哈希和当前的session_ID,因为session_ID会更改每个session_start();只是以为我会指出这一点。
Partack 2011年

3
抱歉,我很无聊,但是第二个cookie somethingELSE的目的是什么?在这种情况下,id是什么?仅仅是一种简单的“ true / false”值即可表明用户是否要使用“保持登录状态”功能?如果是这样,为什么不检查一下Cookie SOMETHING是否首先存在?如果用户不想保留他们的登录名,那么SOMETHING cookie根本就不会存在吗?最后,作为一种额外的安全措施,您是否再次动态生成哈希并针对cookie和数据库进行检查?
itsmequinn 2012年

4
令牌应为RANDOM,不得以任何方式与用户/他的IP /他的用户代理/相连。这是主要的安全漏洞。
帕米尔(Pamil)2013年

4
为什么要使用两种盐?md5(salt + username + ip + salt)
Aaron Kreider

77

介绍

您的标题为“保持登录状态”-最佳方法使我很难知道从哪里开始,因为如果您正在寻找最佳方法,则必须考虑以下事项:

  • 身份证明
  • 安全

饼干

Cookies是易受攻击的,在常见的浏览器cookie盗窃漏洞和跨站点脚本攻击之间,我们必须接受cookie不安全。为了帮助提高安全性,您必须注意php setcookies具有其他功能,例如

bool setcookie(字符串$ name [,字符串$ value [,整数$ expire = 0 [,字符串$ path [,字符串$ domain [,布尔$ secure =假[,布尔$ httponly =假]]]]]]]))

  • 安全(使用HTTPS连接)
  • httponly(通过XSS攻击减少身份盗用)

定义

  • 令牌(长度为n的不可预测的随机字符串,例如/ dev / urandom)
  • 参考(长度为n的不可预测的随机字符串,例如/ dev / urandom)
  • 签名(使用HMAC方法生成键控哈希值)

简单方法

一个简单的解决方案是:

  • 用户使用“记住我”登录
  • 带有令牌和签名的登录Cookie
  • 返回时,检查签名
  • 如果签名没问题..则在数据库中查找用户名和令牌
  • 如果无效..返回登录页面
  • 如果有效,自动登录

以上案例研究总结了此页面上给出的所有示例,但它们的缺点是

  • 无法知道Cookie是否被盗
  • 攻击者可能是访问敏感的操作,例如更改密码或数据(例如个人和烘烤信息等)。
  • 被入侵的Cookie在Cookie的生命周期内仍然有效

更好的解决方案

更好的解决方案是

  • 用户已登录并记住我已被选中
  • 生成令牌和签名并存储在cookie中
  • 令牌是随机的,仅对单次验证有效
  • 每次访问该网站时都将替换令牌
  • 当非登录用户访问该站点时,签名,令牌和用户名将被验证
  • 请记住,我的登录名应该具有访问权限,并且不允许修改密码,个人信息等。

范例程式码

// Set privateKey
// This should be saved securely 
$key = 'fc4d57ed55a78de1a7b31e711866ef5a2848442349f52cd470008f6d30d47282';
$key = pack("H*", $key); // They key is used in binary form

// Am Using Memecahe as Sample Database
$db = new Memcache();
$db->addserver("127.0.0.1");

try {
    // Start Remember Me
    $rememberMe = new RememberMe($key);
    $rememberMe->setDB($db); // set example database

    // Check if remember me is present
    if ($data = $rememberMe->auth()) {
        printf("Returning User %s\n", $data['user']);

        // Limit Acces Level
        // Disable Change of password and private information etc

    } else {
        // Sample user
        $user = "baba";

        // Do normal login
        $rememberMe->remember($user);
        printf("New Account %s\n", $user);
    }
} catch (Exception $e) {
    printf("#Error  %s\n", $e->getMessage());
}

使用的类

class RememberMe {
    private $key = null;
    private $db;

    function __construct($privatekey) {
        $this->key = $privatekey;
    }

    public function setDB($db) {
        $this->db = $db;
    }

    public function auth() {

        // Check if remeber me cookie is present
        if (! isset($_COOKIE["auto"]) || empty($_COOKIE["auto"])) {
            return false;
        }

        // Decode cookie value
        if (! $cookie = @json_decode($_COOKIE["auto"], true)) {
            return false;
        }

        // Check all parameters
        if (! (isset($cookie['user']) || isset($cookie['token']) || isset($cookie['signature']))) {
            return false;
        }

        $var = $cookie['user'] . $cookie['token'];

        // Check Signature
        if (! $this->verify($var, $cookie['signature'])) {
            throw new Exception("Cokies has been tampared with");
        }

        // Check Database
        $info = $this->db->get($cookie['user']);
        if (! $info) {
            return false; // User must have deleted accout
        }

        // Check User Data
        if (! $info = json_decode($info, true)) {
            throw new Exception("User Data corrupted");
        }

        // Verify Token
        if ($info['token'] !== $cookie['token']) {
            throw new Exception("System Hijacked or User use another browser");
        }

        /**
         * Important
         * To make sure the cookie is always change
         * reset the Token information
         */

        $this->remember($info['user']);
        return $info;
    }

    public function remember($user) {
        $cookie = [
                "user" => $user,
                "token" => $this->getRand(64),
                "signature" => null
        ];
        $cookie['signature'] = $this->hash($cookie['user'] . $cookie['token']);
        $encoded = json_encode($cookie);

        // Add User to database
        $this->db->set($user, $encoded);

        /**
         * Set Cookies
         * In production enviroment Use
         * setcookie("auto", $encoded, time() + $expiration, "/~root/",
         * "example.com", 1, 1);
         */
        setcookie("auto", $encoded); // Sample
    }

    public function verify($data, $hash) {
        $rand = substr($hash, 0, 4);
        return $this->hash($data, $rand) === $hash;
    }

    private function hash($value, $rand = null) {
        $rand = $rand === null ? $this->getRand(4) : $rand;
        return $rand . bin2hex(hash_hmac('sha256', $value . $rand, $this->key, true));
    }

    private function getRand($length) {
        switch (true) {
            case function_exists("mcrypt_create_iv") :
                $r = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);
                break;
            case function_exists("openssl_random_pseudo_bytes") :
                $r = openssl_random_pseudo_bytes($length);
                break;
            case is_readable('/dev/urandom') : // deceze
                $r = file_get_contents('/dev/urandom', false, null, 0, $length);
                break;
            default :
                $i = 0;
                $r = "";
                while($i ++ < $length) {
                    $r .= chr(mt_rand(0, 255));
                }
                break;
        }
        return substr(bin2hex($r), 0, $length);
    }
}

在Firefox和Chrome中进行测试

在此处输入图片说明

优点

  • 更好的安全性
  • 攻击者访问受限
  • Cookie被盗后,仅对单次访问有效
  • 当下一个原始用户访问该站点时,您可以自动检测并通知用户盗窃

坏处

  • 不支持通过多个浏览器(移动和网络)的持久连接
  • cookie仍然可以被窃取,因为用户仅在下次登录后才收到通知。

快速解决

  • 为必须具有持久连接的每个系统引入批准系统
  • 使用多个Cookie进行身份验证

多Cookie方法

当攻击者要窃取cookie时,只需将其集中在特定的网站或域上即可。example.com

但实际上您可以对来自2个不同域(example.comfakeaddsite.com)的用户进行身份验证,并使其看起来像“广告Cookie”

  • 用户以记住我的身份登录到example.com
  • 在Cookie中存储用户名,令牌,参考
  • 将用户名,令牌,参考存储在数据库中。记忆快取
  • 通过get和iframe将参考ID发送到fakeaddsite.com
  • fakeaddsite.com使用引用从数据库中获取用户和令牌
  • fakeaddsite.com存储签名
  • 当用户返回通过iframe从fakeaddsite.com获取签名信息时
  • 合并数据并进行验证
  • .....你知道剩下的

有人可能会怀疑您如何使用2种不同的Cookie?好吧,想象example.com = localhostfakeaddsite.com = 192.168.1.120。如果您检查Cookie,它将看起来像这样

在此处输入图片说明

从上图

  • 当前访问的站点是localhost
  • 它还包含从192.168.1.120设置的cookie

192.168.1.120

  • 只接受已定义 HTTP_REFERER
  • 仅接受来自指定的连接 REMOTE_ADDR
  • 没有JavaScript,没有内容,但是除了签名信息之外没有任何内容,只能从Cookie中添加或检索

优点

  • 您有99 %%的时间欺骗了攻击者
  • 您可以轻松地将帐户锁定在攻击者的首次尝试中
  • 像其他方法一样,即使在下次登录之前也可以防止攻击

坏处

  • 仅一次登录即可向服务器发出多个请求

改善

  • 完成使用iframe使用 ajax

5
尽管@ircmaxell很好地描述了该理论,但我还是喜欢这种方法,因为它不需要存储用户ID(这是不想要的公开信息)就可以很好地工作,并且除了用户ID和哈希值之外,还包括更多的指纹来识别用户。用户,例如浏览器。这使得攻击者更难利用被盗的cookie。到目前为止,这是我所见过的最好,最安全的方法。+1
MarcelloMönkemeyer15年

6

在这里从一个角度问了这个问题,答案将带您到您需要的所有基于令牌的超时cookie链接。

基本上,您不会在cookie中存储userId。您存储了一个一次性令牌(巨大的字符串),用户使用该令牌来接回他们的旧登录会话。然后,为了使其真正安全,您需要输入密码以进行繁重的操作(例如更改密码本身)。


6

旧线程,但仍然是一个值得关注的问题。我注意到有关安全性的一些很好的响应,并避免使用“默默无闻的安全性”,但是在我看来,给出的实际技术方法还不够。我贡献自己的方法之前必须说的话:

  • 切勿以明文形式存储密码...永远!
  • 永远不要将用户的哈希密码存储在数据库中的多个位置。您的服务器后端始终能够从用户表中提取哈希密码。代替其他数据库事务存储冗余数据并不是更有效,相反是事实。
  • 您的会话ID的应该是唯一的,所以没有两个用户可以永远共享一个ID,一个ID的,因此目的(可能你的驾驶执照身份证号码永远匹配另一个人?号)这会产生一个两件独特的组合,基于2唯一的字符串。您的会话表应使用ID作为PK。要允许信任多个设备进行自动登录,请为信任设备使用另一个表,该表包含所有已验证设备的列表(请参见下面的示例),并使用用户名进行映射。
  • 将已知数据散列到cookie中没有用,可以复制cookie。我们正在寻找一种合规的用户设备,以提供可靠的信息,而攻击者必须在不损害用户机器的情况下才能获得它们(同样,请参见我的示例)。但是,这意味着合法用户禁止其机器的静态信息(例如,MAC地址,设备主机名,受浏览器限制的用户代理等)保持一致(或首先欺骗它)。使用此功能。但是,如果这是一个问题,请考虑为您提供自动登录功能的用户这一事实,这些用户可以唯一标识自己的身份,因此,如果他们拒绝通过欺骗其MAC,欺骗其用户代理,欺骗/更改其主机名,隐藏在代理之后等来获知,则它们是无法识别的,并且永远不应对其进行身份验证以进行自动服务。如果需要,您需要查看与客户端软件捆绑在一起的智能卡访问权限,该客户端软件可以为正在使用的设备建立身份。

综上所述,有两种方法可以在您的系统上进行自动登录。

首先,一种便宜,简单的方法将所有这些都放在别人身上。如果您使网站支持使用google +帐户登录,则可能有一个简化的google +按钮,如果用户已经登录google,则可以登录(我一直在这里回答这个问题,因为我一直登录到Google)。如果您希望用户在已经使用受信任和受支持的身份验证器登录后自动登录,并且选中了该复选框,请在加载之前让客户端脚本在相应的“登录方式”按钮后面执行代码,只需确保服务器在自动登录表中存储一个唯一ID,即可包含用户名,会话ID和用于用户的身份验证器。由于这些登录方法使用AJAX,因此无论如何您都在等待响应,该响应要么是经过验证的响应,要么是拒绝。如果您收到经过验证的响应,请照常使用它,然后照常继续加载登录的用户。否则,登录将失败,但不要告诉用户,只是继续以未登录状态,他们会注意到。这是为了防止窃取Cookie(或伪造Cookie以试图提升特权)的攻击者得知用户自动登录该站点。

这很便宜,并且可能被某些人认为是肮脏的,因为它试图在未告知您的情况下验证您可能已经在Google和Facebook之类的网站上进行自我登录。但是,不应将其用于没有要求自动登录您的网站的用户,并且该特定方法仅用于外部身份验证,例如Google+或FB。

因为使用了外部身份验证器来告诉服务器在后台验证用户是否经过验证,所以攻击者无法获得除唯一ID之外的任何东西,唯一的ID本身是无用的。我会详细说明:

  • 用户“ joe”首次访问网站,会话ID放置在cookie“ session”中。
  • 用户'joe'登录,提升特权,获取新的Session ID并更新cookie'session'。
  • 用户“ joe”选择使用google +自动登录,并在cookie“ keepmesignedin”中获得了唯一的ID。
  • 用户“ joe”已使Google保持登录状态,从而使您的网站可以在您的后端使用google自动登录该用户。
  • 攻击者系统地尝试为“ keepmesignedin”(这是分发给每个用户的公共知识)的唯一ID,而不是在其他任何地方登录;尝试赋予“ joe”的唯一ID。
  • 服务器收到“ joe”的唯一ID,并在数据库中为Google+帐户提取匹配项。
  • 服务器将Attacker发送到登录页面,该页面向Google运行AJAX请求以进行登录。
  • Google服务器收到请求,使用其API查看Attacker当前未登录。
  • Google发送的响应是当前没有通过此连接登录的用户。
  • 攻击者的页面收到响应,脚本自动使用URL中编码的POST值重定向到登录页面。
  • 登录页面获取POST值,将“ keepmesignedin”的cookie发送到一个空值,并在1970年1月1日之前有效,以阻止自动尝试,从而使攻击者的浏览器仅删除该cookie。
  • 攻击者将获得正常的首次登录页面。

无论如何,即使攻击者使用了不存在的ID,尝试也应该在所有尝试中失败,除非收到确认的响应。

对于使用外部身份验证器登录到您网站的用户,此方法可以并且应该与您的内部身份验证器结合使用。

=========

现在,对于您自己的可以自动登录用户的身份验证器系统,这就是我的方法:

DB有一些表:

TABLE users:
UID - auto increment, PK
username - varchar(255), unique, indexed, NOT NULL
password_hash - varchar(255), NOT NULL
...

请注意,用户名的长度为255个字符。我的服务器程序将系统中的用户名限制为32个字符,但是外部身份验证器的用户名中@ domain.tld可能会更大,因此我只支持电子邮件地址的最大长度以实现最大兼容性。

TABLE sessions:
session_id - varchar(?), PK
session_token - varchar(?), NOT NULL
session_data - MediumText, NOT NULL

请注意,此表中没有用户字段,因为登录时用户名位于会话数据中,并且程序不允许空数据。session_id和session_token可以使用随机的md5哈希,sha1 / 128/256哈希,添加了随机字符串然后进行哈希处理的datetime时间戳生成,也可以根据需要生成,但是输出的熵应保持在可容忍的范围内。避免暴力攻击甚至落地,在尝试添加会话类之前,应检查会话表中生成的所有哈希是否匹配。

TABLE autologin:
UID - auto increment, PK
username - varchar(255), NOT NULL, allow duplicates
hostname - varchar(255), NOT NULL, allow duplicates
mac_address - char(23), NOT NULL, unique
token - varchar(?), NOT NULL, allow duplicates
expires - datetime code

MAC地址本质上应该是唯一的,因此每个条目都具有唯一值是有意义的。另一方面,主机名可以合法地在单独的网络上重复。有多少人将“家用PC”用作他们的计算机名称之一?用户名由服务器后端从会话数据中获取,因此无法进行操作。至于令牌,应使用与生成页面会话令牌相同的方法在Cookie中为用户自动登录生成令牌。最后,为用户何时需要重新验证其凭据添加了日期时间代码。在用户登录时更新此日期时间,以使其在几天之内保持不变,或者无论最后一次登录如何,都强制使其过期,仅将其保留一个月左右,这取决于您的设计要求。

这样可以防止某人为他们认识的自动登录用户系统地欺骗MAC和主机名。让用户使用其密码,明文或其他方式保存cookie。与会话令牌一样,在每个页面导航上重新生成令牌。这极大地降低了攻击者获得有效令牌cookie并将其用于登录的可能性。有人会说攻击者可以从受害者那里窃取cookie并进行会话重播攻击以登录。如果攻击者可以窃取cookie(可能),则他们肯定会破坏整个设备,这意味着他们无论如何都可以使用该设备登录,这完全破坏了窃取cookie的目的。只要您的站点运行在HTTPS上(在处理密码,CC号码或其他登录系统时,它就应该运行),您就可以在浏览器中为用户提供所有保护。

要记住的一件事:如果使用自动登录,则会话数据不应过期。您可以终止继续错误地继续会话的功能,但是,如果系统是持久性数据,并且应该在会话之间继续进行,则验证进入系统后应该恢复会话数据。如果您同时需要持久性和非持久性会话数据,请使用另一个表来存储以用户名作为PK的持久性会话数据,并让服务器像正常会话数据一样检索它,只需使用另一个变量即可。

以这种方式完成登录后,服务器仍应验证会话。在这里,您可以为被盗或受感染的系统编写期望代码。模式和登录会话数据的其他预期结果通常可以得出这样的结论,即系统被劫持或伪造了cookie以获取访问权限。在这里,您的ISS Tech可以制定规则,触发帐户锁定或自动将用户从自动登录系统中删除,从而使攻击者保持足够长的时间,以便用户确定攻击者如何成功以及如何将其切断。

作为结束语,请确保任何恢复尝试,密码更改或登录失败超出阈值都会导致自动登录被禁用,直到用户正确验证并确认已发生这种情况为止。

如果有人期望在我的答案中给出代码,我深表歉意,这不会在这里发生。我会说我使用PHP,jQuery和AJAX来运行我的站点,并且我从不使用Windows作为服务器。



4

生成一个散列(可能只有您知道的秘密),然后将其存储在您的数据库中,以便可以与用户关联。应该工作得很好。


这是在创建用户时创建的唯一标识符,还是在用户每次生成新的“保持登录状态” cookie时都会更改?
马修

1
蒂姆·詹森(Tim Jansson)的答案描述了一种生成哈希的好方法,尽管如果不包含密码,我会感到更安全
贾尼·哈蒂卡宁

2

我的解决方案是这样的。它不是100%防弹的,但我认为它将在大多数情况下为您节省。

用户成功登录后,使用以下信息创建一个字符串:

$data = (SALT + ":" + hash(User Agent) + ":" + username 
                     + ":" + LoginTimestamp + ":"+ SALT)

加密$data,将类型设置为HttpOnly并设置cookie。

当用户回到您的网站时,请执行以下步骤:

  1. 获取cookie数据。删除Cookie中的危险字符。用:角色爆炸。
  2. 检查有效性。如果cookie早于X天,则将用户重定向到登录页面。
  3. 如果cookie不老;从数据库获取最新的密码更改时间。如果在用户上次登录后更改密码,则将用户重定向到登录页面。
  4. 如果最近没有更改通行证;获取用户当前的浏览器代理。检查是否(currentUserAgentHash == cookieUserAgentHash)。如果代理相同,则转到下一步,否则重定向到登录页面。
  5. 如果所有步骤均成功通过,则授权用户名。

如果用户退出,请删除此cookie。如果用户重新登录,则创建新的cookie。


2

我不了解将加密内容存储在Cookie中的概念,因为它是您需要进行黑客入侵的加密版本。如果我缺少什么,请发表评论。

我正在考虑采用这种方式来“记住我”。如果您看到任何问题,请发表评论。

  1. 创建一个表来存储“记住我”数据-与用户表分开,以便我可以从多个设备登录。

  2. 成功登录后(选中“记住我”):

    a)生成一个唯一的随机字符串以用作此计算机上的UserID:bigUserID

    b)生成唯一的随机字符串:bigKey

    c)存储一个cookie:bigUserID:bigKey

    d)在“记住我”表中,添加一条记录:UserID,IP地址,bigUserID,bigKey

  3. 如果尝试访问需要登录的内容:

    a)检查cookie并搜索具有匹配IP地址的bigUserID和bigKey

    b)如果找到它,请登录该人,但在用户表“ soft login”中设置一个标志,这样,对于任何危险的操作,您都可以提示您进行完全登录。

  4. 注销后,将该用户的所有“记住我”记录标记为已过期。

我唯一看到的漏洞是;

  • 您可能会拿着某人的笔记本电脑,并用cookie欺骗他们的IP地址。
  • 您可以每次都欺骗一个不同的IP地址并猜测整个内容-但是要匹配两个大字符串,那将...与上面进行类似的计算...我不知道...赔率很高?

您好,感谢您的回答,我很喜欢。但是,一个问题是:为什么必须生成2个随机字符串-bigUserID和bigKey?您为什么不只生成1并使用它?
杰里米·贝洛洛

2
bigKey将在预定义的时间后过期,但是bigUserID不会。bigUserID允许您在同一IP地址的不同设备上进行多个会话。希望有道理-我不得不考虑片刻:)
Enigma Plus

使用hmac可以帮助解决的一件事是,如果您发现hmac被篡改,则可以肯定地知道有人试图窃取cookie,那么您可以重置每个登录状态。我对吗?
Suraj Jain


0

实施“保持登录状态”功能意味着您需要准确定义对用户意味着什么。在最简单的情况下,我将用它来表示会话的超时时间更长:(例如)2天而不是2小时。为此,您将需要自己的会话存储(可能在数据库中),因此您可以为会话数据设置自定义到期时间。然后,您需要确保设置的Cookie会保留几天(或更长时间),而不是在它们关闭浏览器时过期。

我听到您问“为什么2天?为什么不2周?”。这是因为在PHP中使用会话会自动将到期日推迟。这是因为PHP中的会话有效期实际上是一个空闲超时。

现在,话虽如此,我可能会实现一个更硬的超时值,该值存储在会话本身中,并在2周左右的时间内超时,并添加代码以查看该值并强制使会话无效。或者至少将其注销。这意味着将要求用户定期登录。雅虎!做这个。


1
设置更长的会话可能是不好的,因为它浪费了服务器资源,并且会对性能产生不利影响
user3091530 2015年

0

我认为您可以这样做:

$ cookieString = password_hash($ username,PASSWORD_DEFAULT);

将$ cookiestring存储在数据库中,并将其设置为cookie。还要将该人的用户名设置为cookie。哈希的全部要点是它不能被反向工程。

当用户出现时,从一个cookie获取用户名,而不是从另一个cookie获取$ cookieString。如果$ cookieString与数据库中存储的$ cookieString相匹配,则对用户进行身份验证。由于password_hash每次都使用不同的盐,因此与明文内容无关。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.