php - 会话/cookie 用户定期注销
问题描述
我的用户已经从我的网站登出。当有人从另一台设备登录时会发生这种情况。我调查了一下,注意到来自远程 IP 的人已经能够以不同的用户身份登录我的网站。
我不知道我是否故意成为目标,或者是否是我的代码中的某种错误。我想知道如何以安全的方式正确设置会话 cookie 以防止这种情况发生。
我的 cookie 只是 HTTP
我也不认为这是中间人攻击,因为来自不同区域的多个用户正在注销。我的网站是 SSL 安全的。
我不相信这是暴力攻击。有时当我登录时,在 30 秒内我再次注销并且远程 IP 已登录。
我不相信他可以访问任何密码。一切都在数据库中进行哈希处理,唯一存储在客户端上的是会话 HTTP ONLY cookie 令牌。
我很困。
这是我的登录脚本,它检查用户的凭据并设置会话:
//database connection is $db_connect
//Creates a random String
function generateRandomString() {
$length = rand(25, 30);
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$charactersLength = strlen($characters);
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$randomString .= $characters[rand(0, $charactersLength - 1)];
}
return $randomString;
}
//Grab email, password, IP address
$email_attempt=strtoupper(preg_replace('#[^a-z0-9\.\@\_\-\+]#i', '', $_POST['e']));
$password_attempt=$_POST["p"];
$user_IP = preg_replace('#[^0-9.\:]#', '', getenv('REMOTE_ADDR'));
//Call the database
$sql = "SELECT password, userID FROM user_data WHERE email='$email_attempt' LIMIT 1";
$query = mysqli_query($db_connect, $sql);
if(mysqli_num_rows($query)>0)
{
//Get the ID and hashed password from database
while($row = $query->fetch_assoc()) {
$password_db=$row["password"];
$userID_db=$row["userID"];
}
//Verify Login using password verify
if(password_verify($password_attempt, $password_db)==true){
//remove any previous sessions for the user
$sql = "DELETE FROM user_sessions WHERE userID='$userID_db'";
$query = mysqli_query($db_connect, $sql);
//Clear any current cookies from client
if(isset($_COOKIE["user"]) && isset($_COOKIE["token"])) {
setcookie("user", '', strtotime( '-5 days' ), '/');
setcookie("token", '', strtotime( '-5 days' ), '/');
}
session_destroy();
// Set Session data to an empty array
$_SESSION = array();
//create a new token, set new session and cookies
$token=generateRandomString();
$_SESSION['user'] = $userID_db;
$_SESSION['token'] = $token;
setcookie("user", $userID_db, strtotime( '+3 days' ), "/", "", "", TRUE);
setcookie("token", $token, strtotime( '+3 days' ), "/", "", "", TRUE);
//Hash the token to store in the database
$token_hash=password_hash($token, PASSWORD_DEFAULT);
//Store session into database
$sql = "INSERT INTO user_sessions
(userID, session_token, IP, loginDate)
VALUES
('$userID_db','$token_hash','$user_IP', '$current_date')";
$query = mysqli_query($db_connect, $sql);
header("Location: dashboard.php");
}
else
{
echo 'wrong_credentials';
}
}
这是我通过检查会话和 cookie来评估用户并查看他们是否已登录的代码;
//start session
session_start();
$user_ok = false;
$clientID = "";
$token = "";
$user_IP = preg_replace('#[^0-9.\:]#', '', getenv('REMOTE_ADDR'));
if(isset($_SESSION["user"]) && isset($_SESSION["token"])) {
//Get session
$clientID = preg_replace('#[^a-z0-9]#i', '', $_SESSION['user']);
$token = preg_replace('#[^a-z0-9]#i', '', $_SESSION['token']);
// Verify the user with session data
$user_ok = checkUser($db_connect,$clientID,$token,$user_IP);
} else if(isset($_COOKIE["user"]) && isset($_COOKIE["token"])){
//Set session from cookie
$_SESSION['user'] = preg_replace('#[^a-z0-9]#i', '', $_COOKIE['user']);
$_SESSION['token'] = preg_replace('#[^a-z0-9]#i', '', $_COOKIE['token']);
//Get session data
$clientID = preg_replace('#[^a-z0-9]#i', '', $_SESSION['user']);;
$token = preg_replace('#[^a-z0-9]#i', '', $_SESSION['token']);
// Verify the user
$user_ok = checkUser($db_connect,$clientID,$token,$user_IP);
}
// User Verify function
function checkUser($db_connect,$user,$token,$ip){
//Grab the session
$sql = "SELECT session_token FROM user_sessions WHERE userID='$user' AND ip='$ip' LIMIT 1";
$query = mysqli_query($db_connect, $sql);
if(mysqli_num_rows($query)>0){
$row=mysqli_fetch_row($query);
$token_hashed=$row[0];
//compare token given and hashed token
if(password_verify($token,$token_hashed)==true)
{
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
所以我的问题是:这段代码足够安全吗?
我怎样才能更安全?
另外,如果您对这个谜题中发生的事情有任何想法,请告诉我,因为我正在被摧毁。先感谢您。
解决方案
经过一番挖掘,我意识到问题在于试图将会话锚定到单个 IP 地址。
我们在我们的站点上运行 CloudFlare,因此getenv('REMOTE_ADDR')
有时会返回 CloudFlare 的 IP 地址而不是客户端 IP 地址。这导致了注销。
但我想我可以根据评论回答我自己的问题。为了使脚本更安全并停止定期注销,我应该:
- 始终使用准备好的语句来防止 SQL 注入
- 不要将会话绑定到 IP 地址,因为它们可能会发生变化(尤其是在移动设备上)
奥卡姆剃刀是解决问题的原则,它本质上表明更简单的解决方案比复杂的解决方案更可能是正确的。