【问题标题】:Session/cookie users getting periodically logged out会话/cookie 用户定期注销
【发布时间】:2019-10-11 11:57:56
【问题描述】:

我的用户已从我的网站注销。当有人从另一台设备登录时会发生这种情况。我调查了一下,注意到来自远程 IP 的人已经能够以不同的用户身份登录我的网站。

我不知道我是否故意成为目标,或者我的代码中是否存在某种错误。我想知道如何以安全的方式正确设置会话 cookie 以防止这种情况发生。

  1. 我的 cookie 仅支持 HTTP

  2. 我也不认为这是中间人攻击,因为来自不同区域的多个用户正在注销。我的网站受 SSL 保护。

  3. 我不认为这是暴力攻击。有时当我登录时,在 30 秒内我再次注销并且远程 IP 已登录。

  4. 我不相信他可以访问任何密码。一切都在数据库中散列,唯一存储在客户端上的是会话 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;
    }
}

所以我的问题是:这段代码足够安全吗?

我怎样才能更安全?

此外,如果您对这个谜题中发生的事情有任何想法,请告诉我,因为我正在被摧毁。提前谢谢你。

【问题讨论】:

  • 我很好奇:$sql = "SELECT password, userID FROM user_data WHERE email='$email_attempt' LIMIT 1"; 这足以防止PHP中的sql注入吗?
  • 使用准备好的语句来防止 SQL 注入,而不是临时字符串清理。
  • 我认为您是在说黑客正在检测登录并对其做出响应。如果这是真的,那么至少他们已经破坏了客户端和服务器之间网络的某些部分。 (他们可以检测到 SSL 连接的建立并从中推断出登录,即使他们无法窃听受保护的数据。)但是,他们很可能已经破坏了服务器本身。
  • “当有人从另一台设备登录时会发生这种情况。我查看了它并注意到来自远程 IP 的人已经能够以不同的用户身份登录我的网站。”无论如何,这在您的系统中是一个严重的设计缺陷。人们难以置信地拥有多个 IP 地址,即使通过一个会话也是如此。您会在有多个 Internet 网关的大型企业环境中经常看到这种情况。也有人在 WiFi 和移动设备之间切换。而且,IPv6 用户经常使用不同的地址来保护隐私。
  • 您的 SQL 转义方法存在严重缺陷。毫无疑问,许多扫描 Web 服务器漏洞的机器人之一偶然发现了您的代码。无论数据来自何处,始终使用准备好的/参数化查询。

标签: php mysql session session-cookies


【解决方案1】:

经过一番挖掘,我意识到问题在于尝试将会话锚定到单个 IP 地址。

我们的站点上运行着 CloudFlare,因此getenv('REMOTE_ADDR') 有时会返回 CloudFlare 的 IP 地址而不是客户端 IP 地址。这导致了注销。

但我认为我可以根据 cmets 回答我自己的问题。为了使脚本更安全并停止定期注销,我应该:

  1. 始终使用准备好的语句来防止 SQL 注入
  2. 不要将会话与 IP 地址绑定,因为它们可能会发生变化(尤其是在移动设备上)

奥卡姆剃刀是解决问题的原则,它本质上是说简单的解决方案比复杂的解决方案更可能是正确的。

【讨论】:

  • 不确定您使用的是什么网络服务器,但通常有一种方法可以设置受信任的上游代理列表,在这种情况下,您的网络服务器将使用请求标头值 X-Forwarded-For它发送给 PHP 的远程地址。
猜你喜欢
  • 1970-01-01
  • 2012-06-11
  • 2018-03-01
  • 2012-04-19
  • 2012-01-21
  • 2018-08-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多