Php 用PDO和准备好的语句替换mysql_*函数

Php 用PDO和准备好的语句替换mysql_*函数,php,mysql,database,pdo,prepared-statement,Php,Mysql,Database,Pdo,Prepared Statement,我一直都在做简单的连接,mysql\u connect,mysql\u pcconnect: $db = mysql_pconnect('*host*', '*user*', '*pass*'); if (!$db) { echo("<strong>Error:</strong> Could not connect to the database!"); exit; } mysql_select_db('*database*'); 现在我知道这在某种

我一直都在做简单的连接,
mysql\u connect
mysql\u pcconnect

$db = mysql_pconnect('*host*', '*user*', '*pass*');

if (!$db) {
    echo("<strong>Error:</strong> Could not connect to the database!");
    exit;
}

mysql_select_db('*database*');
现在我知道这在某种程度上是安全的

它逃避危险人物;但是,它仍然容易受到其他攻击,这些攻击可能包含安全字符,但可能对显示数据或在某些情况下恶意修改或删除数据有害

所以,我搜索了一下,找到了关于PDO、MySQLi和prepared语句的信息。是的,我可能会迟到,但我已经阅读了很多很多教程(tizag、W3C、博客、谷歌搜索),没有一本提到这些。这似乎很奇怪,因为仅仅逃避用户输入是不安全的,至少可以说是不好的做法。是的,我知道你可以用正则表达式来解决这个问题,但我很确定这还不够

据我所知,当用户输入变量时,使用PDO/prepared语句是从数据库存储和检索数据的一种更安全的方法。唯一的问题是,转换(特别是在我以前的编码方式/习惯被卡住之后)有点困难

现在我明白了,要使用PDO连接到我的数据库,我将使用

$hostname = '*host*';
$username = '*user*';
$password = '*pass*';
$database = '*database*'

$dbh = new PDO("mysql:host=$hostname;dbname=$database", $username, $password);

if ($dbh) {
    echo 'Connected to database';
} else {
    echo 'Could not connect to database';
}
现在,函数名不同了,因此我的
mysql\u查询
mysql\u获取数组
mysql\u num\u行
等不再工作。所以我不得不读/记一大堆新的,但这就是我感到困惑的地方

如果我想从(比如)注册/登记表中插入数据,我将如何进行,但主要是如何安全地进行?我假设这就是准备好的语句的用武之地,但是通过使用它们,是否就不需要使用类似于
mysql\u real\u escape\u string的东西了?我知道
mysql\u real\u escape\u string
要求您通过
mysql\u connect
/
mysql\u pcconnect
连接到数据库,所以现在我们也不使用它,这个函数会不会产生错误

我也见过不同的方法来处理PDO方法,例如,我见过
:variable
作为我认为的占位符(如果这是错误的,请抱歉)

但我认为这大概就是从数据库中获取用户应该做什么的想法

$user_id = $_GET['id']; // For example from a URL query string

$stmt = $dbh->prepare("SELECT * FROM `users` WHERE `id` = :user_id");

$stmt->bindParam(':user_id', $user_id, PDO::PARAM_INT);
但是我有两个问题,如果变量不是一个数字,而是一个文本字符串,如果我没有弄错的话,你必须在
PDO:PARAM_STR
之后给出一个长度。但是,如果您不确定用户在putted数据中给出的值,那么如何给出一个设置的长度,它每次都会变化?无论哪种方式,据我所知,显示数据,然后你做

$stmt->execute();

$result = $stmt->fetchAll();

// Either

foreach($result as $row) {
    echo $row['user_id'].'<br />';
    echo $row['user_name'].'<br />';
    echo $row['user_email'];
}

// Or

foreach($result as $row) {
    $user_id = $row['user_id'];
    $user_name = $row['user_name'];
    $user_email = $row['user_email'];
}

echo("".$user_id."<br />".$user_name."<br />".$user_email."");
这样行吗?安全吗?如果它是正确的,我将为
?\u LENGTH.
输入什么值?我完全搞错了吗

更新

到目前为止,我收到的回复非常有用,非常感谢你们!每个人都有一个+1,让我看到了一些不同的东西。很难选择最上面的答案,但我认为“榴霰弹上校”是值得的,因为所有的东西都几乎被覆盖了,甚至进入了其他具有自定义库的阵列,而我并不知道


但多亏了大家:)

我从不为bindParam()或param类型或长度而烦恼

我只是将一组参数值传递给execute(),如下所示:

$stmt = $dbh->prepare("SELECT * FROM `users` WHERE `id` = :user_id");
$stmt->execute( array(':user_id' => $user_id) );

$stmt = $dbh->prepare("INSERT INTO `users` (username, email)
                        VALUES (:username, :email)");
$stmt->execute( array(':username'=>$username, ':email'=>$email) );
这同样有效,而且更容易编码


您可能还对我的演示文稿或我的书感兴趣。

要回答长度问题,指定它是可选的,除非您绑定的参数是存储过程中的OUT参数,因此在大多数情况下可以安全地忽略它

就安全而言,当绑定参数时,转义是在幕后完成的。这是可能的,因为在创建对象时必须创建数据库连接。您还可以免受SQL注入攻击,因为通过准备语句,您可以在用户输入接近语句之前告诉数据库语句的格式。例如:

$id = '1; MALICIOUS second STATEMENT';

mysql_query("SELECT * FROM `users` WHERE `id` = $id"); /* selects user with id 1 
                                                          and the executes the 
                                                          malicious second statement */

$stmt = $pdo->prepare("SELECT * FROM `users` WHERE `id` = ?") /* Tells DB to expect a 
                                                                 single statement with 
                                                                 a single parameter */
$stmt->execute(array($id)); /* selects user with id '1; MALICIOUS second 
                               STATEMENT' i.e. returns empty set. */
因此,就安全性而言,你上面的例子似乎很好


最后,我同意单独绑定参数是一件乏味的事情,通过传递给PDOStatement->execute()的数组也可以有效地绑定参数(请参见)。

感谢您提出的有趣问题。给你:

它避开了危险人物

你的概念完全是错误的。
其实“危险人物”是一个神话,根本没有。 和mysql_real_escape_字符串转义,但仅仅是一个字符串分隔符。从这个定义可以看出它的局限性——它只适用于字符串

但是,它仍然容易受到其他攻击,这些攻击可能包含安全字符,但可能对显示数据或在某些情况下恶意修改或删除数据有害

你把一切都混在这里。
说到数据库

  • 对于字符串,不易受攻击。只要您的字符串被引用和转义,它们就不能“恶意修改或删除数据”。
    *
  • 对于其他数据类型data-是的,它是无用的。但这并不是因为它有点“不安全”,而是因为使用不当
至于显示数据,我想在PDO相关的问题中,它是offtopic,因为PDO也与显示数据无关。

转义用户输入

^^^另一个需要注意的错觉

  • 用户输入与转义完全无关。从前面的定义中可以了解到,您必须转义字符串,而不是任何“用户输入”。因此,再一次:

    • 您有转义字符串,无论其来源如何
    • 逃逸其他类型的数据是没有用的,无论数据的来源是什么
明白了吗?
现在,
$stmt = $dbh->prepare("SELECT * FROM `users` WHERE `id` = :user_id");
$stmt->execute( array(':user_id' => $user_id) );

$stmt = $dbh->prepare("INSERT INTO `users` (username, email)
                        VALUES (:username, :email)");
$stmt->execute( array(':username'=>$username, ':email'=>$email) );
$id = '1; MALICIOUS second STATEMENT';

mysql_query("SELECT * FROM `users` WHERE `id` = $id"); /* selects user with id 1 
                                                          and the executes the 
                                                          malicious second statement */

$stmt = $pdo->prepare("SELECT * FROM `users` WHERE `id` = ?") /* Tells DB to expect a 
                                                                 single statement with 
                                                                 a single parameter */
$stmt->execute(array($id)); /* selects user with id '1; MALICIOUS second 
                               STATEMENT' i.e. returns empty set. */
$sql  = 'SELECT * FROM `users` WHERE `name`=?s AND `type`=?s AND `active`=?i';
$data = $db->getRow($sql,$_GET['name'],'admin',1);
$sql = '
  SELECT *
  FROM `users`
  WHERE
    `name` LIKE :name
    AND `type` = :type
    AND `active` = :active
';
$stm = $db->prepare($sql);

$stm->bindValue(':name', $_GET['name']); // PDO::PARAM_STR is the default and can be omitted.
$stm->bindValue(':type', 'admin'); // This is not possible with bindParam().
$stm->bindValue(':active', 1, PDO::PARAM_INT);

$stm->execute();
...
$sql = 'SELECT * FROM `users` WHERE `id` = :id';
$stm = $db->prepare($sql);
$id = 0;
$stm->bindParam(':id', $id, PDO::PARAM_INT);

$userids = array(2, 7, 8, 9, 10);
foreach ($userids as $userid) {
  $id = $userid;
  $stm->execute();
  ...
}
try {
  $db = new PDO(...);
  $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING)
} catch (PDOException $e) {
  echo 'Oops, something went wrong with the database connection.';
}