4

我目前使用会话将用户登录到我的站点。现在,我想将 cookie 添加到组合中,以便人们可以在几天后访问该站点而不必再次登录。

我不想将密码存储在 cookie 中,或加密任何东西 - 所以我的解决方案是将用户名存储在 cookie 中,并使用某种登录哈希(特定于每个用户的每次登录)创建另一个 cookie。然后,在每个页面的顶部,检查 cookie 和登录会话。如果 cookie 存在但会话不存在,请找到用户名并登录用户。

因此,这将在他们成功登录后发生($row['username']是他们输入的用户名):

$_SESSION['username']=$row[username];
$_SESSION['user_id']=$row['id'];
$loginhash = substr(str_shuffle("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), 0, 32);
$number_of_days = 14;
$date_of_expiry = time() + 60 * 60 * 24 * $number_of_days ;
setcookie( "userlogin", $row['username'], $date_of_expiry, "/" ) ;
setcookie( "loginhash", $loginhash, $date_of_expiry, "/" ) ;

$today=date("Y-m-d");

mysql_query("update members set last_login='$today',loginhash='$loginhash' where id='$row[id]' ") or die(mysql_error());

然后在每个页面的顶部,执行以下操作:

if($_COOKIE['userlogin'] && !isset($_SESSION[user_id])){
    $username=mysql_real_escape_string($_COOKIE['userlogin']);
    $loginhash=mysql_real_escape_string($_COOKIE['loginhash']);
    $result=mysql_query("select * from members where (username='$username' || email='$username') && loginhash='$loginhash' ") or die (mysql_error());
    $row=mysql_fetch_array($result);
    $_SESSION['username']=$row[username];
    $_SESSION['user_id']=$row['id'];
}

然后,当然,在用户手动注销时取消设置 cookie。

我的问题是,这样做是否安全(除了其他人使用用户计算机的危险)?每次登录都会生成随机哈希,因此某人不能仅使用某人的用户名创建 cookie 并以该人身份登录。

登录脚本在 HTTPS 上,因此这方面没有危险。

4

2 回答 2

6

不,这不安全!

我可以看到你这样做的方式有两个主要问题:

  1. 我们不在数据库中存储密码的原因是,如果数据库遭到破坏,攻击者将无法访问我们服务器上的帐户……但这正是您使用loginhash. 如果我破坏了您的数据库,我可以在接下来的 14 天内以任何具有活动 cookie 的用户身份登录,username只需使用数据库中的和来创建一个 cookie loginhash

    您可以通过生成随机密钥,将其存储在 cookie 中,然后通过散列函数运行它bcrypt并将摘要存储在数据库中来纠正此问题。这样,即使您的数据库完全转储,用户也不能像任何其他用户一样简单地登录到您的站点。当然,您必须更改比较运算符,以便它比较摘要而不是原始。

  2. str_shuffle功能不是加密安全的。它在rand内部使用这是相当可预测的,并且会使此哈希比您的密码更容易攻击向量。相反,在您的操作系统上使用随机熵(URANDOM 指的是 Unblocking Random,它不如 RANDOM 安全,但因为您的系统将进行大量处理 - 从而产生熵 - 就足够了),如下所示:

改编自此密码重置类

function generateRandomBase64String($length = 32)
{
  if (!defined('MCRYPT_DEV_URANDOM')) die('The MCRYPT_DEV_URANDOM source is required (PHP 5.3).');
  $binaryLength = (int)($length * 3 / 4 + 1);
  $randomBinaryString = mcrypt_create_iv($binaryLength, MCRYPT_DEV_URANDOM);
  $randomBase64String = base64_encode($randomBinaryString);
  return substr($randomBase64String, 0, $length);
}

现在,我们需要让 cookie jacking 变得更加困难

本文解释了 Drupal 如何处理登录令牌,在每次用户使用凭据而不是使用 cookie 登录时存储username一个和loginhash。和可以使用上述随机函数生成series_idloginhashseries_id

当用户登录时,会检查数据库。如果username,loginhashseries_id都存在,则用户已登录,并生成一个新的 cookieusername,其中包含相同series_id但新的loginhash. 这意味着每个 cookie 只能使用一次。

此外,以这种方式登录的用户在没有“正确”密码登录的情况下无法执行任何妥协功能,例如请求密码重置、提取资金和查看敏感信息。这是为了保护 cookie 被劫持的用户。

如果在series_id存在但loginhash不匹配的地方检测到登录,我们会清除该用户的所有有效 cookie,并给他留下措辞强硬的消息,表明使用了无效 cookie 来访问他的帐户。

附带说明重新mysql_*功能

这似乎是新代码,尚未部署。如果回头还为时不晚,请考虑将数据库驱动程序切换到mysqli文档),它保持非常相似的程序风格,mysql但允许您使用绑定参数和其他现代功能来提高数据安全性。

于 2013-08-19T14:42:20.130 回答
1

有几点需要改进:

  1. 首先,最好使用操作系统随机源 (URANDOM) 生成登录令牌。您的代码将只使用每个字符一次,并且 shuffle 函数可能基于当前时间(随机数)。知道大概的登录时间,将因此严重缩小可能的组合。

  2. 令牌不应以明文形式存储在数据库中,而应仅将其哈希值存储在数据库中。如果有人可以读取您的数据库(SQL 注入),他仍然无法使用该代码。

  3. 该函数setcookie()有两个参数$secure$httponly,您应该将两者都设置为 true。否则,浏览器可能会被欺骗将未加密的令牌发送到 HTTP 页面(它甚至会被发送到图像请求)。

于 2013-08-19T14:46:24.600 回答