我的 PHP 登录系统有多安全?

Posted

技术标签:

【中文标题】我的 PHP 登录系统有多安全?【英文标题】:How secure is my PHP login system? 【发布时间】:2012-01-31 05:48:15 【问题描述】:

我是 php 新手,这也是我第一次登录系统,所以如果你们能查看我的代码,看看是否能发现任何安全漏洞,那就太好了:

注意:尽管此处未显示,但我正在清理所有用户输入。

注册:

第 1 步:我获取用户选择的密码并通过此函数运行它:

encrypt($user_chosen_password, $salt);

function encrypt($plain_text, $salt) 
    if(!$salt) 
        $salt = uniqid(rand(0, 1000000));
    
    return array(
        'hash' => $salt.hash('sha512', $salt.$plain_text),
        'salt' => $salt
    );

第 2 步:然后我将哈希和盐值($password['hash']$password['salt'])存储在数据库的 users 表中:

id | username | password  | salt       | unrelated info...
-----------------------------------------------------------
1  | bobby    | 809a28377 | 809a28377f | ...
                fd131e5934
                180dc24e15
                bbe5f8be77
                371623ce36
                4d5b851e46

登录:

第 1 步:我使用用户输入的用户名并在数据库中查找是否返回任何行。在我的网站上,没有 2 个用户可以共享相同的 用户名,因此用户名字段始终具有唯一值。如果我返回 1 行,我会为该用户获取盐。

第 2 步: 然后我通过 encrypt 函数运行用户输入的密码(如前所述),但这次我还提供了从数据库中检索到的 salt:

encrypt($user_entered_password, $salt);

第 3 步:我现在有了正确的密码来匹配这个变量:$password['hash']。所以我对数据库进行了第二次查找,看看是否 输入的用户名和散列密码一起返回一行。如果是,那么用户的凭据是正确的。

第 4 步:为了在用户通过凭据后登录,我生成了一个随机唯一字符串并对其进行哈希处理:

$random_string = uniqid(rand(0, 1000000));
$session_key = hash('sha512', $random_string);

然后我将$session_key 插入到数据库中的active_sessions 表中:

user_id | key
------------------------------------------------------------
1       | 431b5f80879068b304db1880d8b1fa7805c63dde5d3dd05a5b

第 5 步:

我将在最后一步中生成的未加密的唯一字符串 ($random_string) 设置为我称之为 active_session 的 cookie 的值:

setcookie('active_session', $random_string, time()+3600*48, '/');

第 6 步:

在我的header.php 顶部有这个检查:

if(isset($_COOKIE['active_session']) && !isset($_SESSION['userinfo'])) 
   get_userinfo();

get_userinfo() 函数在数据库中查找users 表并返回一个关联数组,该数组存储在名为userinfo 的会话中:

//首先这个函数获取active_session cookie的值并对其进行散列得到session_key:

hash('sha512', $random_string);

// 然后它在active_sessions 表上进行查找以查看此key 的记录是否存在,如果存在,它将获取与该记录关联的user_id 并使用它来进行第二次查找users 表获取userinfo

    $_SESSION['userinfo'] = array(
        'user_id'           => $row->user_id,
        'username'          => $row->username,
        'dob'               => $row->dob,
        'country'           => $row->country,
        'city'              => $row->city,
        'zip'               => $row->zip,
        'email'             => $row->email,
        'avatar'            => $row->avatar,
        'account_status'    => $row->account_status,
        'timestamp'         => $row->timestamp,
    ); 

如果userinfo 会话存在,我知道用户已通过身份验证。如果它不存在但 active_session cookie 存在,则检查 header.php 文件顶部的将创建该会话。

我使用 cookie 而不是单独使用会话的原因是为了保持登录。因此,如果用户关闭浏览器,会话可能会消失,但 cookie 仍然存在。由于header.php 的顶部有该检查,因此将重新创建会话,并且用户可以作为已登录的 在用户中正常。

退出:

第 1 步:userinfo 会话和 active_session cookie 均未设置。

第 2 步:删除数据库中active_sessions 表中的关联记录。


注意:我能看到的唯一问题(也许还有很多其他问题)是用户是否通过自己在浏览器中创建 active_session cookie 来伪造它。当然,他们必须将一个字符串设置为该 cookie 的值,该字符串在加密后必须与 active_sessions 表中的记录匹配,我将从该表中检索 user_id 以创建该会话。我不确定这种情况的实际可能性有多大,对于用户(可能使用自动化程序)正确猜测他们不知道的字符串然后将被 sha512 加密并与active_sessions 表中的字符串匹配数据库以获取用户 ID 以构建该会话。

很抱歉这篇大文章,但由于这是我网站的关键部分,而且由于我缺乏经验,我只想让更有经验的开发人员运行它,以确保它实际上是安全的。

那么您是否发现这条路线存在任何安全漏洞以及如何改进?

【问题讨论】:

我会说很明显 - 如果您不使用 HTTPS 传输用户名/密码,那么您很容易受到任何数量的攻击(编辑:除此之外,我个人可以'没有看到任何缺陷。但这只是图片的一小部分。复杂性要求等也很好,以及检查所有站点代码路径) @Kieren Johnstone 是的,但我不会在这个网站上使用 SSL,至少在开始时。 那么你将不需要的长度,因为从一开始密码就会以明文形式传输。如果您没有执行任何这些操作,那么第一步将是使用 SSL/HTTPS。这是第一步.. 确实是一个小问题,考虑所有因素,但考虑使用mt_rand() 而不是rand() 请注意hashing 和encryption 完全不同:加密文本可以使用正确的密钥解密,而散列文本不能反转(单向函数)。 【参考方案1】:

您应该包括某种超时或故障转移,以防止暴力攻击。有很多方法可以做到这一点,包括基于 IP 的阻止、增量超时等。这些方法都不会阻止黑客,但它们会让黑客变得更加困难。

另一点(你没有提到,所以我不知道你的计划)是失败消息。使失败消息尽可能模糊。提供诸如“该用户名存在,但密码不匹配”之类的错误消息可能对最终用户有所帮助,但它扼杀登录功能。您刚刚将应该花费O(n^2) 时间的蛮力攻击转​​换为O(n) + O(n)。不需要尝试彩虹表中的每个排列(例如),黑客只需首先尝试用户名的所有值(使用设置的密码),直到失败消息发生变化。然后,它知道一个有效的用户,并且只需要暴力破解密码。

按照这些思路,您还应该确保用户名存在和不存在时经过的时间相同。当用户名实际存在时,您正在运行其他进程。因此,当用户名存在时响应时间会比不存在时更长。一个非常熟练的黑客可以对页面请求进行计时以找到有效的用户名。

同样,您应该确保除了使 cookie 过期外,还应使会话表过期。

最后,在get_user_info() 调用中,如果有多个并发的活动登录,您应该终止所有打开的会话。确保在一定数量的不活动(如 30 分钟)后超时会话。

按照@Greg Hewgill 提到的内容,您没有包括以下任何内容:

服务器-客户端之间的 SSL/加密连接 您经常用于处理身份验证的其他传输协议(如 OAuth)

您的服务器 是安全的,但如果有人可以读取交换的数据 (MITM),那么您的算法的安全性并不重要。您应该确保仅通过加密协议进行通信。

【讨论】:

是的,我计划引入 x 次失败尝试登录超时。至少这会延迟自动机器人尝试蛮力攻击。 讨厌当网站告诉我“密码与电子邮件地址不匹配”,然后当我尝试发送无意义的电子邮件时,“用户不存在”。而且...当网站通过电子邮件向我发送我“忘记”的密码时。谢谢。我见过一些所谓的风险意识强的网站这样做,但拒绝承认这样做有问题。【参考方案2】:

...通过加密功能运行用户输入的密码...

那么密码是如何从浏览器到服务器的呢?您还没有提到防止中间人攻击。

【讨论】:

你是指通过用户网络监听的人,比如需要 SSL? 是的,没错。如果我可以看到浏览器和服务器之间的数据包(有很多方法),那么我可以看到用户的密码是明文的。【参考方案3】:

这段代码...

function encrypt($plain_text, $salt) 
    if(!$salt) 
        $salt = uniqid(rand(0, 1000000));
    
    return array(
        'hash' => $salt.hash('sha512', $salt.$plain_text),
        'salt' => $salt
    );

...不好。 Use the new password API 并完成它。除非您是专家,否则您不应该尝试设计自己的身份验证系统。 They are extremely difficult to get right.

为了在用户通过凭据后登录,我生成了一个随机唯一字符串并对其进行哈希处理:

只需let PHP handle session management。 rand()mt_rand() 都是非常不安全的随机数生成器。

【讨论】:

【参考方案4】:

您创建的代码似乎无法通过自动化单元和集成测试进行测试。这使得随着时间的推移以及在生产环境中运行时,很难发现可能包含在您的实施中的任何错误。

这通常会导致安全问题,因为严格而正确的执行和健全的数据处理的安全性未经测试/验证。

(这只是列表中的另一点,请参阅有关如何保护传输层的答案,您还没有指定如何保​​护会话数据不被篡改。)

Authentication Cheat Sheet Session Management Cheat Sheet

【讨论】:

这是一个有趣的答案。您是否有任何其他链接或信息可以帮助解释如何解决此问题? 我更喜欢一个例子:如果 Paula 在 db 中具有相同的 pw-hash 和 hash,她也许能够获得 Bobby 的身份。单元测试抽象了商店,很容易通过边界测试发现此类问题,例如重复值。这可能是一个在实现过程中被忽略的细节,因为希望一切都正确。这种情况最好尽早发现。这只是标准测试的东西,所以你应该在网上找到很多关于这个主题的东西。除此之外,我建议nostarch.com/codecraft.htm 好吧,我想说,大多数对单元测试和集成测试如何有益感兴趣的人都是一次性的小型团队,他们可能没有实践支持看实际回报。我想知道,数据库中的pw-hashhash 相同 是什么意思?这是碰撞测试吗? 密码和盐。这是一个测试,如果它们跨行碰撞会发生什么,是的。我不明白你写的关于团队的部分等等。 如果您担心应用程序的某个单元,您想知道它是否正常工作,您需要对其进行测试。单独审查是行不通的。我不是说你需要做 TDD,而是说你需要测试它。即使是这样的组件,您也需要尝试打破它。【参考方案5】:

关于 php 中的密码,你不应该加密它们。您应该使用password_hash() 对它们进行哈希处理,然后在登录时使用password_verify() 验证通过html 表单输入的密码是否与数据库中存储的哈希值匹配

【讨论】:

以上是关于我的 PHP 登录系统有多安全?的主要内容,如果未能解决你的问题,请参考以下文章

安全的PHP登录系统?

WebSockets + PHP(棘轮)登录系统

PHP登录系统:记住我(持久cookie)[重复]

PHP登录系统不发送?

PHP + SQL 登录系统。返回错误的值[重复]

如何用 PHP 制作一个简单的登录系统? [关闭]