PHP中使用$_POST和$_GET变量的最佳实践


Best practices in PHP using $_POST and $_GET variables

考虑到一个项目将由多个开发人员工作,并且将不断更新和维护,考虑到可读性和安全性,以下两个代码中的哪一个可以被认为是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 ) ...

然而,我应该重申,"自制卫生设施"是一个可怕的前景,其他评论者已经非常详细地阐述了这一点。