2

我目前正在为一个网络项目实施“记住我”系统。在阅读了一些(尽管不是那么有用的文章)之后,我想出了这个系统来防止 cookie 被用于访问分配给的用户以外的帐户:

  1. 用户登录并选中“记住我”框。
  2. 用户电子邮件和密码组合已通过身份验证。因为要检查记住我功能,所以会创建一个长度为 128 个字符的随机盐并将其与用户用户 ID 一起存储在一个表中。设置一个 cookie,其中包含用户 userid 的散列连接到用户 userid 和 salt 的散列。
  3. 当用户重新访问该网页时,用户 ID 和哈希是分开的,并且哈希是根据存储在数据库中的盐和用户 ID 生成的哈希来检查的。如果值匹配,则用户是分配 cookie 的用户并且用户已登录。

如果有任何问题/可以改进,请指出我总是乐于批评:)

4

5 回答 5

3

您的系统有一个良好的开端,但仍然很容易受到劫持(如果有人获取了存储在 cookie 中的哈希,他可以轻松接管会话)。

无论如何,这里有一些关于这个主题的好帖子:

我认为您应该从阅读这些链接开始。

确实没有一个完整的万无一失的系统(查看第 3 和第 4 个链接,它们提供了一些有趣的额外内容;用户代理是您也可以使用的某种标识符)。我可以建议您使用某种 OpenID 提供程序(或多个)来摆脱所有安全问题。如果确实想自己做:您必须权衡利弊以实现最大的安全性或更少。

于 2012-06-18T21:57:28.467 回答
2

就像其他人说的那样,你有一个好的开始。我建议创建一个存储一些 cookie 信息的表,特别是:

user_id
random_hash
ip_address
date_created

然后,当用户登录时,您会像您所说的那样生成一个哈希,并将该数据插入到表中并创建存储他们的 user_id 和哈希的 cookie。

当用户返回时,您检查哈希和 ip_address 并查找匹配项。如果找到一个,则验证所述 user_id删除使用的散列并为下次登录生成一个新散列。我相信你可以看到这样做的好处。

另外 - 确保您使用 SSL 并且(重要)绝不允许用户在不提供密码的情况下更改或查看任何关键信息!

干杯!

于 2012-06-18T22:16:52.110 回答
1

不,这太复杂了。

  1. 在包含列的数据库中创建remember_me(或您喜欢的任何表名)表user_id | hash

  2. 使用“记住我”选项对用户进行身份验证后,生成随机哈希并将其与对应的 user_id 和用户的 cookie 一起放入表中

  3. 当用户来到一个页面并且没有经过身份验证并且具有“remember_me”cookie - 通过 cookie 值查找 user_id

重要的是:没有正当理由将您的 remember_me 客户端数据依赖于真实用户名、电子邮件、密码或任何东西

PS:如果您愿意,可以将客户端数据附加到 user_id 值

于 2012-06-18T21:55:25.970 回答
0

是的,这听起来不错,但为什么要重新发明轮子呢?您正在描述会话。您也可以使用 PHP 内置的功能。

http://php.net/manual/en/features.sessions.php

于 2012-06-18T21:53:45.590 回答
0

我想我找到了一个聪明的解决方案!

这个(复杂的?)脚本的优点:

  • 当用户在选中记住我的情况下成功登录时,除了标准会话管理 cookie 之外,还会发出一个登录 cookie。[2]

  • 登录 cookie 包含用户的用户名、系列标识符和令牌。系列和令牌是来自适当大空间的不可猜测的随机数。这三者一起存储在数据库表中。

  • 当未登录用户访问该站点并显示登录 cookie 时,将在数据库中查找用户名、系列和令牌。

  • 如果存在三元组,则认为用户已通过身份验证。使用的令牌将从数据库中删除。生成一个新令牌,并使用用户名和相同的系列标识符存储在数据库中,并向用户发出一个包含所有三个的新登录 cookie。

  • 如果存在用户名和系列但令牌不匹配,则假定为盗窃。用户会收到措辞强硬的警告,并且用户记住的所有会话都将被删除。

  • 如果用户名和系列不存在,则忽略登录 cookie。

我在数据库中创建了一个包含以下信息的表:

session | token | username | expire

记住我的 cookie 将具有以下设置:

$value = "$session|$token|$userhash"; //Total length = 106
  • Session将是一个 40 (sha1) 个字符的字符串。
  • Token将是一个 32 (md5) 个字符的字符串。
  • Userhash在 cookie 中将是 32 个(用户名的 md5)字符的字符串。
  • Username在数据库中将是正常的用户名。
  • Expire现在将是 + 60 天。

剧本:

ini_set('session.hash_function', 'sha1');
ini_set('session.hash_bits_per_character', '4');

session_start();

if(isset($_POST['user']) && isset($_POST['password'])) {
    if(isset($_COOKIE['remember']) && strlen($_COOKIE['remember']) == 106) {
      //THERE is a cookie, which is the right length 40session+32token+32user+2'|'
      //Now lets go check it...
      //How do I protect this script form harmful user input?
      $plode = explode('|', $_COOKIE['remember']);
      $session = htmlspecialchars($plode[0]);
      $token = htmlspecialchars($plode[1]);
      $userhash = htmlspecialchars($plode[2]);
      $result_query = $auth->query("SELECT user
                  FROM sessions
                  WHERE session = '$session'
                  AND token = '$token'
                  AND user = '$userhash'");
      $result_array = array();

      $auth_query = $auth->query("SELECT user FROM sessions WHERE session = '$session' AND user = '$userhash'");
      $auth_array = array();

      while ($result_object = $result_query->fetch(PDO::FETCH_NUM)) {
          $result_array[] = $result_object;
      }

      while ($auth_object = $auth_query->fetch(PDO::FETCH_NUM)) {
          $auth_array[] = $auth_object;
      }

      if(count($result_array) > 0){
        if(isset($_COOKIE['PHPSESSID'])) {
          //COOKIE is completely valid!
          //Make a new cookie with the same session and another token.
          $newusername = $auth_array[0][0];
          $newsession = $session;
          $newtoken = md5(uniqid(rand(), true));
          $newuserhash = $newusername;
          $value = "$newsession|$newtoken|$newuserhash";
          $expire = time() + 4184000;
          setcookie('remember', $value, $expire, '/', 'spigotpool.ml', isset($_SERVER["HTTPS"]), true);
          $auth_query = $auth->prepare("UPDATE sessions
                  SET token = :newtoken, expire=:expire
                  WHERE session = :session
                  AND token = :token
                  AND user = :userhash");
          $auth_query->bindParam(':newtoken', $newtoken);
          $auth_query->bindParam(':expire', $expire);
          $auth_query->bindParam(':session', $session);
          $auth_query->bindParam(':token', $token);
          $auth_query->bindParam(':userhash', $userhash);
          $auth_query->execute();
          //Set-up the whole session (with user details from database) etc...
        }
      } else if(count($auth_array) == 1) {
          //TOKEN is different, session is valid
          //This user is probably under attack
          //Put up a warning, and let the user re-validate (login)
          //Remove the whole session (also the other sessions from this user?)
      } else {
          //Cookie expired in database? Unlikely...
          //Invalid in what way?
          //Make a new cookie with the same session and another token.
          $newusername = $_POST['user'];
          $newsession = session_id();
          $newtoken = md5(uniqid(rand(), true));
          $newuserhash = md5($newusername);
          $value = "$newsession|$newtoken|$newuserhash";
          $expire = time() + 4184000;
          setcookie('remember', $value, $expire, '/', 'www.example.com', isset($_SERVER["HTTPS"]), true);
          $auth->query("INSERT INTO sessions (token, expire, session, user) VALUES ('$newtoken', '$expire', '$newsession', '$newuserhash')");
          header('Location: index.php?action=logged-in');
      }
    } else {
      //No cookie, rest of the script
      //Make a new cookie with the same session and another token.
      $newusername = $_POST['user'];
      $newsession = session_id();
      $newtoken = md5(uniqid(rand(), true));
      $newuserhash = md5($newusername);
      $value = "$newsession|$newtoken|$newuserhash";
      $expire = time() + 4184000;
      setcookie('remember', $value, $expire, '/', 'www.example.com', isset($_SERVER["HTTPS"]), true);
      $auth->query("INSERT INTO sessions (token, expire, session, user) VALUES ('$newtoken', '$expire', '$newsession', '$newuserhash')");
      header('Location: index.php?action=logged-in');
    }
}

脚本的优点:

  • 多次登录。您可以为您所在的每台计算机创建新会话。
  • Cookie 和数据库将保持清洁。活跃用户每次登录都会更新 cookie。
  • 一开始的会话检查确保数据库不会收到无用的请求。
  • 如果攻击者窃取了 cookie,它会获得一个新的令牌,但不会获得一个新的会话。因此,当真正的用户使用旧的(无效的)令牌但使用有效的用户会话组合访问网站时,用户会收到潜在盗窃的警告。通过登录重新验证后,会创建一个新会话,并且攻击者持有的会话无效。重新验证确保受害者确实是受害者,而不是攻击者。

参考:http: //jaspan.com/improved_persistent_login_cookie_best_practice

于 2019-09-28T14:46:28.483 回答