如何在 PHP 中安全地实现“记住我”按钮(持久登录)
Posted
技术标签:
【中文标题】如何在 PHP 中安全地实现“记住我”按钮(持久登录)【英文标题】:How to securely implement the "Remember me" button in PHP (persistent login) 【发布时间】:2018-02-18 00:30:42 【问题描述】:首先是一些背景知识,我还在学习 php,我正在尝试构建一个 CMS(免责声明:仅供我自己使用)。
我还在测试一些东西,我已经成功创建了一个管理员登录系统。
所以,现在它是这样工作的:
用户登录,在验证和清理输入后,我检查用户是否存在,并使用 PHPs password_verify
将密码与存储在数据库中的哈希密码进行比较。
如果登录成功,我会将管理员重定向到 dashboard.php
,并创建一个名为 adminId
的会话变量,并将用户 ID 存储在该变量中。
这是我的第一个问题:
攻击者可以更改$_SESSION['adminId']
的值吗?例如,从 1 更改为 12,因此以不同的管理员身份登录?
无论如何,我已经阅读了this article,其中介绍了攻击者可以使用的多个漏洞。
所以,如果我没记错的话,我应该使用 cookie 进行持久登录,对吗?
好的,所以第一件事是永远不要将用户 ID 存储在 cookie 中并使用它来检查登录,因为攻击者可以很容易地更改它吗?
好的,所以我可以创建一个随机令牌(使用 random_bytes 然后转换为十六进制)并将其与数据库中 logins
表中的用户 ID 配对。强>
例如,我有这个:
token: 2413e99262bfa13d5bf349b7f4c665ae2e79e357,
userId: 2
因此,用户登录,创建令牌,并使用 userId 存储在数据库中。 假设用户关闭了浏览器。 然后他再次打开它,里面只有带有令牌的 cookie:2413e99262bfa13d5bf349b7f4c665ae2e79e357。如此自动地,它使用 userId: 2 登录用户,对吗?
如果他想以 userId: 10 的用户身份登录,他需要知道与该用户配对的随机令牌。
很好,但是有这个问题:时序泄漏。所以他们提出了一个使用两个东西的解决方案,一个选择器和一个验证器。看不懂的时候,文章是这样说的:
以下是我们提出的策略,用于在 Web 应用程序中处理“记住我”cookie,而不会将任何有用信息(甚至时间信息)泄露给攻击者,同时仍然快速高效(以防止拒绝服务攻击)。
我们提出的策略在一个关键方面偏离了上述简单的基于令牌的自动登录系统:我们存储
selector:validator
,而不是仅在 cookie 中存储随机的token
。
selector
是一个唯一 ID,便于数据库查找,同时防止不可避免的时间信息影响安全。 (这比简单地使用数据库id
字段更可取,它会泄露应用程序上的活动用户数。)在数据库方面,
validator
不是批发存储的;相反,validator
的 SHA-256 散列存储在数据库中,而明文存储(使用selector
)在用户的 cookie 中。有了这种故障保护措施,如果auth_tokens
表以某种方式泄露,就可以防止立即进行广泛的用户模拟。自动登录算法类似于:
将
selector
与validator
分开。 获取auth_tokens
中给定选择器的行。如果没有找到,则中止。 使用 SHA-256 对用户 cookie 提供的validator
进行哈希处理。 使用hash_equals()
将我们生成的 SHA-256 哈希与存储在数据库中的哈希进行比较。 如果第 4 步通过,请将当前会话与相应的用户 ID 相关联。
所以这就是我不明白的地方:
1) selector
和 validator
应该包含什么?
2) 为什么这个系统是安全的以及它如何防止时序泄漏?
在此先感谢,很抱歉发了这么长的帖子,但我真的很想了解它是如何工作的以及如何实现它。
我知道我可以使用一些框架或库,但我想从头开始,通过反复试验学习!
【问题讨论】:
在我看来,考虑到这种情况,时间攻击不太可能发生。但是,您可能需要查看 JSON Web Tokens (JWT) 来解决您的问题。您只需将令牌存储在用户端,并且由于它是从您的服务器发出的(理想情况下具有到期日期/时间),因此您可以控制身份验证令牌的发出和维护。 那么,假设我不处理时序泄漏,而我采用令牌方法,我完全按照我在帖子中所说的去做,它安全吗? 假设您在合理的时间后使令牌过期(从而防止重放攻击),是的。 【参考方案1】:攻击者可以更改
$_SESSION['adminId']
的值吗?例如,从 1 更改为 12,因此以不同的管理员身份登录?
没有。会话数据仅存储在服务器上。客户端只有标识会话的字符串。
我应该使用 cookie 进行持久登录,对吗?
如果您要进行持久登录,通常是这样做的。
所以第一件事是永远不要将用户 ID 存储在 cookie 中并使用它来检查登录,因为攻击者可以轻松更改它吗?
正确,您永远不想公开任何关键数据,例如用户 ID。
1)
selector
和validator
应该包含什么?
每个都是正确生成的随机字符串(例如使用openssl_random_pseudo_bytes
)。 selector
必须是唯一的。
2) 为什么这个系统是安全的以及它如何防止时序泄漏?
与生成一个基本令牌并直接查找它相比,它增加了两个安全功能。
首先,如果您的数据库泄露给攻击者,他们将无法生成自己的记住我的 cookie 并模拟每个用户。 validator
仅作为散列值存储在数据库中。 cookie 必须包含未散列的值。反转一个好的哈希非常耗时。
其次,比较仅在服务器上生成的哈希可以避免定时攻击。如果攻击者发送各种记住我的 cookie 值来计时他们的查找,它不会暴露任何有用的信息,因为服务器上的比较是针对散列值而不是原始值。
一般来说,如果您的系统安全性如此重要,我建议您完全不要支持记住我功能。如果您确实想在重要站点上使用此功能,请使用已经过安全测试的库。
【讨论】:
您好,感谢您的回答。关于选择器,我只有一个问题。现在我正在使用它生成它:base64_encode(random_bytes(8));
但这不是唯一的吗?如何在不检查数据库并检查它是否已经存在的情况下使其唯一?
@nick 为了保证它是独一无二的,你必须检查数据库。
我不能使用时间戳并将其用作盐或类似的东西(例如将其附加到选择器的末尾)吗?
@nick 是的。尽管从理论上讲,这使得它更可预测。但坦率地说,如果您的网站被小心攻击的几率非常高,那么您无论如何都不会使用记住我的 cookie。所以我个人认为使用时间戳+盐生成很好。以上是关于如何在 PHP 中安全地实现“记住我”按钮(持久登录)的主要内容,如果未能解决你的问题,请参考以下文章