我的 PHP 登录系统有多安全?
我是 PHP 新手,这也是我第一次登录系统,所以如果你们能查看我的代码,看看是否能发现任何安全漏洞,那就太好了:
I'm new to PHP and this is also my first log in system so it would be great if you guys could look over my code and see if you can spot any security holes:
注意:尽管此处未显示,但我正在清理所有用户输入.
note: I am sanitizing all user input although it's not shown here.
第 1 步:我使用用户选择的密码并通过此函数运行它:
Step 1: I take the password the user chose and run it through this function:
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']
>) 在数据库中的用户表中:
Step 2: I then store the hash and the salt ($password['hash']
and $password['salt']
) in the users table in the database:
id | username | password | salt | unrelated info...
-----------------------------------------------------------
1 | bobby | 809a28377 | 809a28377f | ...
fd131e5934
180dc24e15
bbe5f8be77
371623ce36
4d5b851e46
登录:
第 1 步: 我使用用户输入的用户名并在数据库中查找是否返回任何行.在我的网站上,没有 2 个用户可以共享相同的内容username 因此用户名字段始终具有唯一值.如果返回 1 行,我会为该用户获取盐.
Log In:
Step 1: I take the username the user entered and do a look up on the database to see if any rows are returned. On my site no 2 users can share the same username so the username field always has a unique value. If I get 1 row returned I grab the salt for that user.
第 2 步: 然后我通过 encrypt 函数运行用户输入的密码(如之前发布的),但这次我还提供了从数据库中检索到的盐:
Step 2: Then I run the user entered password through the encrypt function (as previously posted above) but this time I also supply the salt retrieved from the database:
encrypt($user_entered_password, $salt);
第 3 步:我现在有正确的密码来匹配这个变量:$password['hash']
.所以我对数据库进行了第二次查找,看看是否输入的用户名和散列密码一起返回单行.如果是,那么用户的凭据是正确的.
Step 3: I now have the proper password to match against in this variable: $password['hash']
. So I so a second lookup on the database to see if the
username entered and the hashed password together return a single row. If so then the user's credentials are correct.
第 4 步:为了在用户的凭据通过后登录,我生成了一个随机的唯一字符串并将其散列:
Step 4: In order to log the user in after their credentials passed I generate a random unique string and hash it:
$random_string = uniqid(rand(0, 1000000));
$session_key = hash('sha512', $random_string);
然后我将 $session_key
插入到数据库中的 active_sessions
表中:
I then insert the $session_key
into the active_sessions
table in the database:
user_id | key
------------------------------------------------------------
1 | 431b5f80879068b304db1880d8b1fa7805c63dde5d3dd05a5b
第 5 步:
我将最后一步生成的未加密的唯一字符串 ($random_string
) 设置为我称之为 active_session
的 cookie 的值:
I take the unencrypted unique string generated in the last step ($random_string
) and set that as the value of a cookie which I call active_session
:
setcookie('active_session', $random_string, time()+3600*48, '/');
第 6 步:
在我的 header.php
包含的顶部有这个检查:
At the top of my header.php
include there is this check:
if(isset($_COOKIE['active_session']) && !isset($_SESSION['userinfo'])) {
get_userinfo();
}
get_userinfo()
函数在数据库中的 users
表上进行查找,并返回一个关联数组,该数组存储在名为 userinfo的会话中代码>:
The get_userinfo()
function does a lookup on the users
table in the database and returns an associative array which is stored in a session called userinfo
:
//首先这个函数获取active_session cookie的值并散列它以获得session_key:
// first this function takes the value of the active_session cookie and hashes it to get the session_key:
hash('sha512', $random_string);
//然后它会在 active_sessions
表上进行查找以查看此 key
的记录是否存在,如果存在,它将获取 user_id
与该记录相关联,并使用它对 users
表进行第二次查找以获取 userinfo
:
// then it does a lookup on the active_sessions
table to see if a record by this key
exists, if so it will grab the user_id
associated with that record and use this to do a second lookup on the users
table to get the 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
文件顶部将创建该会话.
If the userinfo
session exists I know the user is authenticated. If it doesn't exist but the active_session
cookie exists then that check
at the top of the header.php
file will create that session.
我使用 cookie 而不是单独使用会话的原因是为了保持登录.因此,如果用户关闭浏览器,会话可能会消失,但cookie 将仍然存在.并且由于在 header.php
的顶部有那个检查,会话将被重新创建并且用户可以作为记录的在用户正常.
The reason why I am using a cookie and not sessions alone is to persist the login. So if the user closes the browser the session may be gone but the
cookie will still exist. And since there is that check at the top of header.php
, the session will be recreated and the user can function as a logged
in user as normal.
第 1 步:userinfo
会话和 active_session
cookie 都未设置.
Step 1: Both the userinfo
session and the active_session
cookie are unset.
步骤 2: 删除数据库中 active_sessions
表中的关联记录.
Step 2: The associated record from the active_sessions
table in the database is removed.
注意:我能看到的唯一问题(也许还有很多其他问题)是用户是否通过在浏览器中自行创建来伪造 active_session
cookie.当然,他们必须将一个字符串设置为该 cookie 的值,该字符串在加密后必须与 active_sessions
表中的记录匹配,我将从该表中检索 user_id
以创建该会话.我不确定这在现实中的可能性有多大,对于用户(可能使用自动化程序)正确猜测他们不知道的字符串然后将被 sha512 加密并与 active_sessions<中的字符串匹配/code> 数据库中的表以获取用户 ID 以构建该会话.
Notes: The only issue I can see (and perhaps there are many others), is if the user fakes that active_session
cookie by creating it themselves in their browser. Of course they must set as that cookie's value a string which after it is encrypted must match a record in the active_sessions
table from where I will retrieve the user_id
to create that session. I am not sure what the chances of this is realistically, for a user (perhaps using an automated program) to guess a string correctly which they don't know will then be sha512 encrypted and matched against the string in the active_sessions
table in the database to get the user id to build that session.
对于这篇大文章很抱歉,但由于这是我网站的关键部分,而且由于我缺乏经验,我只想让更有经验的开发人员运行它,以确保它实际上是安全的.
Sorry for the big essay but since this is such a critical part of my site and due to my inexperience I just wanted to run it by more experienced developers to make sure it's actually safe.
那么您是否发现这条路线存在任何安全漏洞以及如何改进?
So do you see any security holes in this route and how can it be improved?
推荐答案
您应该包括某种超时或故障转移以防止暴力攻击.有很多方法可以做到这一点,包括基于 IP 的阻止、增量超时等.这些方法都不会阻止黑客,但它们会使黑客变得更加困难.
You should include some kind of timeout or failover to prevent against brute-force attacks. There are a number of ways to do this, including IP-based blocking, incremental timeouts, etc. None of these will ever stop a hacker, but they can make it much more difficult.
另一点(你没有提到,所以我不知道你的计划)是失败消息.使失败信息尽可能含糊.提供诸如该用户名存在,但密码不匹配"之类的错误消息可能对最终用户有帮助,但会杀死登录功能.您刚刚将需要 O(n^2)
时间的蛮力攻击转换为 O(n)
+ O(n)
.不需要尝试彩虹表中的每个排列(例如),黑客只需首先尝试用户名的所有值(使用设置的密码),直到失败消息发生变化.然后,它知道一个有效用户,只需要暴力破解密码.
Another point (which you haven't mentioned, so I don't know your plan) is failure messages. Make failure messages as vague as possible. Providing an error message like 'That username exists, but the passwords did not match' might be helpful to the end-user, but it kills login functionality. You just converted a brute-force attack that should take O(n^2)
time to O(n)
+ O(n)
. Instead of needed to try every permutation in a rainbow table (for example), the hacker just tries all values for username (with a set password) first, until the failure message changes. Then, it knows a valid user, and just has to brute force the password.
沿着这些思路,您还应该确保用户名存在和不存在时经过的时间相同.当用户名实际存在时,您正在运行其他进程.因此,当用户名存在与不存在时,响应时间会更长.一个非常熟练的黑客可以计时页面请求以找到有效的用户名.
Along those lines, you should also make sure that the same amount of time elapses when a username exists and doesn't exist. You are running additional processes when a username actually exists. As such the response time would be longer when a username exists vs when it doesn't. An incredibly skilled hacker could time page requests to find a valid username.
同样,您应该确保除了使 cookie 过期外,还应使会话表过期.
Similarly, you should make sure that, in addition to expiring cookies, you also expire the sessions table.
最后,在 get_user_info()
调用中,如果有多个并发的活动登录,您应该终止所有打开的会话.确保在一段时间(如 30 分钟)不活动后超时会话.
Lastly, in the get_user_info()
call, you should terminate all open sessions if there are multiple concurrent, active logins. Make sure you timeout sessions after a set amount of inactivity (like 30 minutes).
根据@Greg Hewgill 提到的内容,您没有包含以下任何内容:
Along the lines of what @Greg Hewgill mentioned, you haven't included any of the following:
- 服务器-客户端之间的 SSL/加密连接
- 您经常用于处理身份验证的其他传输协议(如 OAuth)
您的服务器是安全的,但如果有人可以读取交换的数据 (MITM),那么您的算法有多安全并不重要.您应该确保仅通过加密协议进行通信.
You server is secure, but it doesn't matter how awesomely secure your algorithm is if someone can read the data that's exchanged (MITM). You should make sure you are only communicating over an encrypted protocol.
相关文章