我不是网络安全专家,只是一名网站管理员,我想知道这种身份验证是否会很危险。我使用 PHP 在服务器端测试这样的密码:
if (isset($_POST['pass_word']) AND $_POST['pass_word'] == $passwd)
$passwd
来自我的 PostgreSQL 数据库。我认为至少我不会冒 SQL 注入的风险。我会冒险进行其他类型的注射吗?如果这是一种危险的身份验证方式,请解释原因。
我不是网络安全专家,只是一名网站管理员,我想知道这种身份验证是否会很危险。我使用 PHP 在服务器端测试这样的密码:
if (isset($_POST['pass_word']) AND $_POST['pass_word'] == $passwd)
$passwd
来自我的 PostgreSQL 数据库。我认为至少我不会冒 SQL 注入的风险。我会冒险进行其他类型的注射吗?如果这是一种危险的身份验证方式,请解释原因。
这看起来像您以明文形式存储密码,这是一个坏主意。您应该确保密码至少使用password_hash()
和来保护password_verify()
,它们在后台使用bcrypt。这对于大多数场景来说简单、容易、安全且完全可以接受。
或者,您可以使用稍微更安全的方法,例如Argon2,它赢得了密码哈希竞赛并且能够抵抗 CPU 和 GPU 破解,并且还旨在最大限度地减少侧通道攻击的可能性。有一篇文章它解释了如何使用 Argon2 作为 libsodium 的 PHP 包装器的一部分,或直接使用 Paragon 的“Halite”库,该库为 Argon2 提供了顶部的对称加密,以防止仅数据库访问提供可用的哈希值,因为对称密钥是作为文件存储在服务器的磁盘上。这个选项更复杂,但如果你真的偏执,它确实提供了一些额外的安全性。不过,如果您不熟悉安全开发,我建议您避免这样做,因为您搞砸的机会会增加。
我还建议使用URL 查询或空值中的数组===
来避免奇怪的错误相等情况。
多项式的答案是正确的。我想指出其他潜在的漏洞:运算符优先级、易于审查和易于测试。
某种形式的东西foo AND bar == baz
很容易弄错优先级。我会仔细审查,因为复杂的条件和优先级,安全措施被挫败的历史由来已久。
现在,您所写的内容没有任何问题,但很容易出错。在大多数语言中,这很好,因为比较运算符==
like比逻辑运算符like具有更高的优先级AND
,因此您的条件读取为(注意显式括号):
isset($_POST['pass_word']) AND
($_POST['pass_word'] == $passwd)
...但是很容易弄错。例如,AND
和&&
不具有相同的优先级。如果不是使用isset
您(或跟随您的人)使用快捷方式逻辑或设置默认值怎么办?(这不等同于isset
,它只是导致安全错误的优先级示例)
$_POST['pass_word'] || '' == $passwd
这个问题线实际上是这样的:
$_POST['pass_word'] || ('' == $passwd)
现在,任何拥有空白密码的人(可能是由于检索错误)将始终登录。
在涉及安全代码时,请明确说明您的意图,以使审阅者和程序知道您的意图。
更好的是,在安全代码中完全避免复合条件。你不能用你没用的东西搞砸。例如,此代码利用封装和提前返回的优势,提供了非常易于查看和测试的方法。
// This deals with input and normalizing it.
function can_login($user) {
if( !isset($_POST['pass_word']) ) {
// Or it could throw an exception to give the
// caller more information about why they didn't login
return false;
}
return check_password($user, $_POST['pass_word']);
}
// This only deals with checking a password.
// Don't use this, it still has all the flaws Polynomial
// pointed out.
function check_password($user, $check) {
// get_password() should throw an exception if it cannot
// find that user's password to avoid accidentally thinking
// their password is false or 0 or '' or something. This
// avoids relying on the caller doing the check for failure.
return $check === get_password($user);
}
它可能不是最好的方法,但它很小且易于测试和审查。这将登录用户的详细信息(可能不仅仅是密码检查,就像他们是有效用户一样)与检查他们的密码分开。任何问题都可以被发现并修复,而无需费力完成其余的登录代码。审阅者将检查get_password
所有地方check_password
并can_login
使用。
使用尽可能简单的代码将您的安全组件隔离成尽可能小的一部分,以使它们易于审查和测试,我怎么强调都不为过。
奖励:这是我想做的事情,但认为它会使代码不那么安全。
我最初的想法是让一个函数返回多个值:他们没有提供密码,他们的密码是错误的,他们的密码是正确的。这将使调用者知道“密码错误”和“他们没有提供密码,有问题”之间的区别。
function can_login($user) {
if( !isset($_POST['pass_word']) ) {
return NO_PASSWORD;
}
$password = get_password($user);
if( $_POST['pass_word'] === $password ) {
return YES;
}
else {
return NO;
}
}
然后你这样称呼它:
$rslt = can_login($user);
if( $rslt == YES ) {
echo "Login!\n";
}
else if( $rslt == NO ) {
echo "Wrong password.\n";
}
else if( $rslt == NO_PASSWORD ) {
echo "No password given.\n";
}
问题是它使正确调用例程变得更加复杂。更糟糕的是,如果您不阅读文档,您会认为是这样的:
if( can_login($user) ) {
echo "Yes!\n";
}
else {
echo "No!\n";
}
由于can_login
总是返回一个真值,现在任何人都可以登录。
它强调了这一点:让您的安全代码对审阅者、测试者和调用者来说尽可能简单和万无一失。在这种情况下,异常是正确的方法,因为它们提供了获取更多信息的渠道,同时保持can_login
简单的布尔值。如果忘记检查程序可能会崩溃,但登录失败。