什么是使用登录Cookie的相对安全的方式?

14

我想知道最安全的cookie登录方式是什么? 如果只在cookie中存储加盐加密的密码和用户名,并根据用户表验证,潜在攻击者可以窃取cookie并登录。 人们通常不检查他们的'上次在线时间'。

那么是否有更好的“记住我cookie”方式? IP地址不是一个好的选择,是吗?(一些机器经常更改IP地址)。


1
请告诉我们您的应用程序将如何使用此功能。冒充/身份盗窃可能会产生什么潜在影响? - Gumbo
大多数机器都会不断更改IP地址... - Shoe
2
“记住我”功能在定义上是不安全的。因此,选择在可用性和偏执之间取舍就由您决定了。 - Your Common Sense
1
那不是事实,上校。我可以通过 cookie 来限制用户在登录时的选项(例如,当想要编辑详细信息时必须重新验证),并且我可以限制攻击者的窗口。 - SuperSpy
1
“记住我”功能在技术上并不是不安全的。它可能因为用户与其他人共享设备或者设备丢失而不安全。尽可能地重复使用现有的身份验证框架,因为它确实很复杂。例如,请查看 https://github.com/delight-im/PHP-Auth - caw
3个回答

21

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

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

  • 当用户成功勾选了“记住我”并登录时,除了标准的会话管理 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算法生成。
  • cookie中的Userhash 是一个由32个字符组成的字符串,采用用户名的md5值生成。
  • 数据库中的Username 是普通的用户名。
  • Expire 表示60天后过期。

脚本如下:

if(isset($_SESSION['check']) || $_SESSION['check']){
    //User is logged in
}else 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...
    conncectdb(); //Sets connection
    //How do I protect this script form harmful user input?
    $plode = explode('|',$_COOKIE['remember']);
    $session = mysql_real_escape_string($plode[0]);
    $token = mysql_real_escape_string($plode[1]);
    $userhash = mysql_real_escape_string($plode[2]);
    $result = mysql_query(" SELECT user 
                FROM tokens 
                WHERE session = '$session' 
                AND token = '$token'
                AND md5(user) = '$userhash';")
    if(mysql_num_rows($result)==1){
        //COOKIE is completely valid!
        //Make a new cookie with the same session and another token.
        $newusername = mysql_result($result,0,0);
        $newsession = $session;
        $newtoken = md5(uniqid(rand(), true));
        $newuserhash = md5($username);
        $value = "$newsession|$newtoken|$newuserhash";
        $expire = time()+4184000;
        setcookie('remember', $value, $expire, '/', 'www.example.com', isset($_SERVER["HTTPS"]), true);
        mysql_query("   UPDATE tokens 
                SET token='$newtoken', expire='$expire'
                WHERE session = '$session' 
                AND token = '$token'
                AND md5(user)='$userhash';");
        //Set-up the whole session (with user details from database) etc...
    } else if(mysql_num_rows(mysql_query("SELECT user FROM tokens WHERE session = '$session' AND md5(user) = '$userhash';"))==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?
    }
} else {
    //No cookie, rest of the script
}

脚本的优点:

  • 多重登录。你可以为每台电脑创建新的会话。
  • Cookie 和数据库保持干净。活跃用户会在每次登录时更新他们的 Cookie。
  • 在开始时进行的会话检查确保数据库不会得到无用的请求。
  • 如果攻击者窃取了一个 Cookie,它会得到一个新的令牌,但不是一个新的会话。因此,当真正的用户使用旧(无效的)令牌但带有有效的用户-会话组合访问网站时,用户会收到潜在盗窃的警告。重新验证通过登录创建新会话并使攻击者所持有的会话无效。重新验证确保受害者确实是受害者,而不是攻击者。

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


我看不出这个解决方案有什么意义。为什么在这个表中需要session字段?为什么要专门的表呢?为什么不只存储由某些用户数据生成的哈希值呢? - Your Common Sense
这可以限制攻击者能够使用您的帐户的时间。 - SuperSpy
你读过剧本吗,Col.?你已经在惩罚我了 =S - SuperSpy
1
听起来很像这个技术:http://jaspan.com/improved_persistent_login_cookie_best_practice - Paul Dixon
这确实是我使用的方法 =) 但他们没有添加的一件事是用户需要重新验证(和实际代码)。 他们脚本中的缺陷是,攻击者等待受害者有一个新令牌,然后尝试使用旧令牌进行身份验证,可以销毁所有会话。 - SuperSpy
显示剩余3条评论

6

最流行的方式:

  • 许多脚本使用某种会话跟踪。当用户首次访问网站时,它会为用户生成一个唯一的随机ID,并将会话信息存储在服务器和ID存储在cookie中。然后服务器使用唯一ID(称为会话ID)来识别用户。与会话ID关联的信息只能由服务器看到。PHP默认使用此功能。

  • 有些将用户数据存储在cookie本身中,但使用密钥作为HMAC签名。如果签名不匹配,则脚本会丢弃cookie。这样,服务器就不必在服务器上保存会话数据。用户通过查看cookie来查看会话内容,因此您不应在其中存储敏感数据。仅用户ID(以及可能是登录时间和cookie过期时间)应该足够。虽然用户可以看到会话信息中的内容,但cookie中的签名确保用户不能自行修改会话数据。

这些方法提供了一定的安全性,使用户无法篡改会话数据,但并不能保护用户免受窃听者的攻击。他们始终可以使用数据包嗅探器从任何开放的WiFi网络中窃取会话。一些应用程序确实依赖于用户IP,但如果攻击者在同一网络中,则无关紧要。一些应用程序依赖于User-Agent,但当用户更新其浏览器或从另一个浏览器导入数据时,将会出现问题。

如果您真的关心安全问题,请使用HTTPS

还要阅读这篇文章,特别是名为网站运营者如何解决问题?的部分。


长篇大论,但与主题无关。有人因为字数多而点赞,却并未阅读内容。 - Your Common Sense
1
这个话题不是关于登录cookie及其安全性吗?这就是我在回答中谈论的内容。 - Thai
这完全是相关话题,它给了我一些关于会话ID和HTTPS的想法。我已经点赞了。 - Shoe

6
这样的“记住我”功能总是额外的安全风险。因为就像在会话中一样,你只需要一个标识符就足以不仅识别用户(他/她是谁?),还可以验证该用户(他/她真的是本人吗?)而无需进行实际身份验证。但与会话相反,会话应该只有短暂的生命周期(大多数情况下少于一小时),并且标识符应该定期更改(基于时间和必要性,由于真实性/授权状态更改),而“记住我”标识符的有效期可能长达数天甚至数月或数年!这种长时间的有效期带来了额外的安全风险。因此,在询问如何实现这样的“记住我”功能之前,您应该问自己是否真正想要承担这种额外的安全风险。这主要取决于您的应用程序拥有的资产以及身份验证的目的,以及您是否愿意承担“记住我”功能带来的冒充/身份盗窃风险。
如果需要,一定要提供基本的安全性保障,通过使用 HTTPS 并且设置 HTTPOnly 标志和 secure 在你的 cookies 中设置标志。然后,您可以执行以下操作来构建“记住我”功能:
  • 身份验证请求
    如果用户通过HTTPS进行身份验证并设置了“记住我”选项,则生成一个随机的记住我令牌,在服务器端的“记住我”数据库中存储它,并使用该值设置带有secure标志的记住我 cookie。然后开始一个新会话并设置记住我标志。

  • 任何其他请求

    1. 如果没有当前会话,请通过HTTPS重定向到记住我页面,检查是否存在记住我 cookie。如果存在记住我令牌并且有效,则将其作废,生成一个新的令牌,将其存储在“记住我”数据库中,设置一个带有该新令牌的cookie,并创建一个设置了记住我标志的新会话。否则重定向到登录页面。
    2. 如果当前会话无效(请务必使用严格的会话失效),则如果设置了记住我标志,请通过HTTPS重定向到记住我页面;否则重定向到登录页面。
使用HTTPS来保证身份验证的安全性,包括初始身份验证和“记住我”身份验证。用户只在当前会话期间进行身份验证;如果会话过期,则必须使用“记住我”令牌或提供其登录凭据重新进行身份验证。由于“记住我”令牌存储在数据库中,因此用户可以使任何现有的“记住我”令牌失效。请保留HTML标签。

如果您真的想要额外的安全风险,我可以限制基于cookie的身份验证用户以防止损坏。 请检查下面的脚本,您永远不会得到一个幸运的随机令牌,因为如果令牌相同,则用户必须是相同的两个。 我想让记住我更加持久,而不仅仅是将会话过期固定为更高的值。 - SuperSpy
这是一个答案,但(对我来说)不是唯一的答案。 - SuperSpy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接