考虑到一个项目将由多个开发人员工作,并且将不断更新和维护,考虑到可读性和安全性,以下两个代码中的哪一个可以被认为是PHP的最佳实践?如果我们谈论表演,第二种选择可能会更好,但有办法解决这一点。
选项1
$user = $_POST['user'];
$pass = $_POST['pass'];
// Prevent SQL Injection and XSS
$user = anti_injection($user);
$pass = anti_injection($pass);
if(strlen($user) <= 8 && strlen($pass) <= 12)
{
// SQL query
$sql = "SELECT id
FROM users
WHERE username = '$user' AND password = '$pass';";
}
选项2
// Retrieve POST variables and prevent SQL Injection and XSS
$_POST['user'] = anti_injection($_POST['user']);
$_POST['pass'] = anti_injection($_POST['pass']);
if(strlen($_POST['user']) <= 8 && strlen($_POST['pass']) <= 12)
{
// SQL query
$sql = "SELECT id
FROM users
WHERE username = '" . $_POST['user']. "' AND password = '" . $_POST['pass'] . "';";
}
编辑1
我没有使用MySQL,我的数据库是PostgreSQL
也不要这样做
我只能假设anti_injection
是某种自定义过滤函数,这是个坏主意™.如果真的想采用这两种想法中的任何一种,那么应该使用mysql_real_escape_string
。
编写SQL查询时保持安全的唯一方法是使用参数,例如通过MySQLi。无论如何,mysql_*
函数都已被弃用,因此您最好尽快跨出。
事实上,mysql_real_escape_string
并不是针对注入攻击的万无一失的防御。考虑查询中的整数比较,例如WHERE $var > 30
。我可以成功地将1=1 or 100
注入$var
,并完全打破逻辑。
参数将数据与查询语言完全分离,完全降低了注入风险。服务器接收一个包含参数表示法和一组要插入的值的查询,因此它可以完全不同地处理查询语言和数据。
此外,您似乎在以明文形式存储密码。这是个坏主意。您应该研究一种强大的密码存储哈希算法,如bcrypt,它使从哈希中获得明文密码变得非常困难。
MD5和SHA1不是密码存储的理想选择,因为它们设计得很快,这意味着攻击者甚至可以快速破解强盐水密码。现代GPU每秒可以实现50亿次MD5哈希。事实上,有些人拥有专用的哈希破解设备,其中一些可以以每秒450亿个哈希的速度破解MD5。
您还应该看看这些令人敬畏的问题,它们完全涵盖了SQL注入攻击、密码存储和许多其他安全问题:
- 基于表单的网站认证的权威指南
- 如何防止PHP中的SQL注入
- PHP密码的安全散列和salt
更新:您提到您正在使用postgres。您可以使用PDO从PHP运行参数化查询,如下所述。
这两个选项都不是最佳实践,即使只是针对可疑的anti_injection
,它几乎肯定不会像宣传的那样工作。还有以下问题:
- 在验证输入数据之前对其进行篡改
- 存储明文密码
- 手动构造SQL查询,而不是使用绑定参数
根据项目的范围,最后一个可能是可以接受的。
关于性能,第一个选项理论上会更快,因为它执行的数组索引更少。但这种差异肯定很小,根本看不到。第一个选项也更可读,提供了更好的抽象,所有这些都只需要微不足道的额外内存。
两者都是错误的。您似乎将密码存储为纯文本,这只是在找麻烦。
此外,如果我的用户名是"123456"
,我将无法登录,因为您会将其转义为'"123456'"
,这将无法通过"length<=8"检查。
如果您想要安全性和性能,我建议您使用PDO。使用参数时不能进行sql注入。下面是一个示例,您需要"定义"mysql参数,以便下面的工作。
这也允许数据库缓存您的查询,因为它不会在每次使用不同的参数执行时更改,这也会提高性能。
:p_user and :p_pass
用于表示参数和
array ( ':p_user' => $user, ':p_pass' => $pass ' )
将参数设置为需要传入的值。
你还应该考虑添加一个密码salt,并将其sha1存储在数据库中,这样,如果你的数据库被泄露,你的密码就不会清楚地透露给黑客。
class users{
function __construct() {
define('MySQLHost', 'localhost');
define('MySQLName', 'databasename');
define('MySQLUser', 'username');
define('MySQLPass', 'password');
define('pwsalt', 'tScgpBOoRL7A48TzpBGUgpKINc69B4Ylpvc5Xc6k'); //random characters
}
static function GetQuery($query, $params)
{
$db = new PDO('pgsql:host='.MySQLHost.';dbname='.MySQLName.';', MySQLUser, MySQLPass);
$cmd = $db->prepare($query);
$cmd->execute($params);
if($cmd->rowCount()==0)
return null;
return $cmd->fetchAll();
}
static function GetUser($user, $pass)
{
$query = "select id
from `users`
where username = :p_user and password = :p_pass";
$rows = users::GetQuery($query, array(
':p_user' => $user,
':p_pass' => sha1($pass.pwsalt) //Append the salt to the password, hash it
));
return $rows;
}
}
$user = $_POST['user'];
$pass = $_POST['pass'];
if( strlen($user) <= 8 && strlen($pass) <= 12 )
{
$result = users::GetUser($user, $pass);
if($result != null)
print 'Login Found';
}
虽然我同意其他海报,但不要试图在SQL安全方面"重新发明轮子",问题似乎是性能和如何使用超全局变量。
作为最佳实践,不要更改超全局变量($_GET、$_POST、$_REQUEST、$_SYSTEM等)。我想不出任何违反此规则会提高性能的例子,任何修改都会导致不确定性和混乱。
因此,在这种情况下,两种选择都不正确。Option1不必要地复制了一个变量(根据Google性能文档,这是一个no)。选项2使超全局变量发生突变,这违反了上述准则。相反,做一些类似的事情:
$user = anti_injection( $_POST['user'] );
$pass = anti_injection( $_POST['pass'] );
if( strlen($user) <= 8 && strlen($pass) <= 12 ) ...
然而,我应该重申,"自制卫生设施"是一个可怕的前景,其他评论者已经非常详细地阐述了这一点。