用PDO和预处理语句替换mysql_*函数

no.*_*no. 10 php mysql database pdo prepared-statement

我总是做简单的连接mysql_connect,mysql_pconnect:

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

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

mysql_select_db('*database*');
Run Code Online (Sandbox Code Playgroud)

虽然使用这个我一直使用的简单的方法,使查询之前逃脱的任何数据,不管是INSERT,SELECT,UPDATE或者DELETE通过使用mysql_real_escape_string

$name = $_POST['name'];

$name = mysql_real_escape_string($name);

$sql = mysql_query("SELECT * FROM `users` WHERE (`name` = '$name')") or die(mysql_error());
Run Code Online (Sandbox Code Playgroud)

现在我明白这在某种程度上是安全的!

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

所以,我搜索了一下,发现了PDO,MySQLi和准备好的语句.是的,我可能会迟到,但我已经阅读了很多很多教程(tizag,W3C,博客,谷歌搜索),没有一个人提到这些.看起来很奇怪为什么,因为只是逃避用户输入真的不安全而且至少可以说是不好的做法.是的,我知道你可以使用Regex解决它,但是,我很确定这还不够吗?

据我所知,当用户输入给出变量时,使用PDO /预处理语句是一种更安全的方式来存储和检索数据库中的数据.唯一的麻烦是,切换(特别是在我的方式/先前编码的习惯非常困难之后)有点困难.

现在我明白使用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';
}
Run Code Online (Sandbox Code Playgroud)

现在,函数名是不同的,因此将不再我mysql_query,mysql_fetch_array,mysql_num_rows等工作.所以我必须阅读/记住一些新的,但这是我感到困惑的地方.

如果我想从注册/注册表单中插入数据,我将如何进行此操作,但主要是如何安全地进行此操作?我假设这是预备语句的来源,但通过使用它们,这是否消除了使用类似的东西的需要mysql_real_escape_string?我知道这mysql_real_escape_string需要你通过mysql_connect/ 连接到数据库mysql_pconnect现在我们不使用这个函数不会产生错误吗?

我已经看到了不同的方法来处理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);
Run Code Online (Sandbox Code Playgroud)

但后来我坚持了几件事,如果变量不是一个数字并且是一串文本,PDO:PARAM_STR如果我没有弄错,你必须给出一个长度.但是如果你不确定用户输入数据给出的值,你怎么能给出一个设定的长度,它每次都会变化?无论哪种方式,据我所知,您可以显示数据

$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."");
Run Code Online (Sandbox Code Playgroud)

现在,这一切都安全吗?

如果我是对的,插入数据会是相同的,例如:

 $username = $_POST['username'];
 $email = $_POST['email'];

 $stmt = $dbh->prepare("INSERT INTO `users` (username, email)
                        VALUES (:username, :email)");

 $stmt->bindParam(':username, $username, PDO::PARAM_STR, ?_LENGTH_?);
 $stmt->bindParam(':email, $email, PDO::PARAM_STR, ?_LENGTH_?);

$stmt->execute();
Run Code Online (Sandbox Code Playgroud)

那会有用吗,那也安全吗?如果它是正确的,我会为什么价值?_LENGTH_??我完全错了吗?

UPDATE

到目前为止我的回复非常有帮助,不能谢谢你们!每个人都有一个+1打开我的眼睛到一些有点不同的东西.很难选择最佳答案,但我认为Col. Shrapnel应该得到它,因为一切都被覆盖,甚至进入其他阵列,我不知道自定义库!

但多亏你们所有人:)

You*_*nse 12

感谢有趣的问题.干得好:

它摆脱了危险的角色,

你的概念是完全错误的.
事实上"危险人物"是一个神话,没有.并且mysql_real_escape_string转义但仅仅是一个字符串分隔符.从这个定义中你可以得出结论它的局限性 - 它只适用于字符串.

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

你在这里混合一切.
说到数据库,

  • 对于字符串,它不容易受到攻击.只要您的字符串被引用和转义,它们就无法 "恶意修改或删除数据".*
  • 对于其他数据类型数据 - 是的,它没用.但不是因为它有点"不安全",而是因为使用不当.

至于显示数据,我认为它在PDO相关问题中是offtopic,因为PDO也与显示数据无关.

逃避用户输入

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

  • 用户输入与转义完全无关.正如您可以从前一个定义中学习的那样,您必须转义字符串,而不是"用户输入".所以,再次:

    • 你有逃脱字符串,无论其来源如何
    • 无论来源如何,逃避其他类型的数据都是无用的.

明白了吗?
现在,我希望你了解逃避的局限性以及"危险人物"的误解.

据我所知,使用PDO /预处理语句更安全

并不是的.
实际上,我们可以动态添加四个不同的查询部分:

  • 一个字符串
  • 一个号码
  • 标识符
  • 语法关键字.

所以,你可以看到转义仅涵盖一个问题.(但是,当然,如果您将数字视为字符串(将它们放在引号中),在适用的情况下,您也可以使它们安全)

准备好的陈述涵盖 - 呃 - 整个2个等级!很重要;-)

对于其他2个问题,请参阅我之前的回答,在PHP中,当我向数据库提交字符串时,我应该使用htmlspecialchars()处理非法字符还是使用正则表达式?

现在,函数名称不同,所以我的mysql_query,mysql_fetch_array,mysql_num_rows等不再有效.

这是PHP 用户的另一个严重妄想,一场自然灾害,一场大灾难:

即使使用旧的mysql驱动程序,也不应该在代码中使用裸API函数!一个人必须把它们放在一些库功能中以供日常使用!(不仅仅是为了使代码更短,更少重复,防错,更一致和可读).

PDO也是如此!

现在再次提出你的问题.

但通过使用它们,这是否消除了使用mysql_real_escape_string之类的需要?

是.

但我认为这大致是为了从数据库中获取用户应该做些什么

不是要获取,而是向查询添加任何数据!

你必须在PDO之后给出一个长度:PARAM_STR如果我没有弄错的话

你可以,但你不必.

现在,这一切都安全吗?

就数据库安全而言,此代码中没有任何弱点.没有什么可以保证在这里.

显示安全性 - 只需在此网站上搜索XSS关键字.

希望我对此事有所了解.

顺便说一句,对于长插入,你可以使用我有一天写的函数,使用PDO插入/更新辅助函数

但是,我现在没有使用预先准备好的陈述,因为我更喜欢使用我上面提到的库,而不是我自己的家庭酿造占位符.因此,为了对抗下面的riha发布的代码,它将与这两行一样短:

$sql  = 'SELECT * FROM `users` WHERE `name`=?s AND `type`=?s AND `active`=?i';
$data = $db->getRow($sql,$_GET['name'],'admin',1);
Run Code Online (Sandbox Code Playgroud)

但是,当然您也可以使用预准备语句使用相同的代码.


* (yes I am aware of the Schiflett's scaring tales)


Bil*_*win 8

我从不打扰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) );
Run Code Online (Sandbox Code Playgroud)

这同样有效,也更容易编码.

您可能也对我的演示文稿SQL注入神话和谬误,或我的书SQL反模式:避免数据库编程的陷阱感兴趣.


rih*_*iha 5

是的,:某些东西是PDO中的命名占位符,?是一个匿名占位符.它们允许您逐个绑定值或一次绑定所有值.

所以,基本上这有四个选项来为您的查询提供值.

使用bindValue()一个接一个

一旦调用它,就会将具体值绑定到占位符.您甚至可以绑定硬编码字符串,bindValue(':something', 'foo')如果需要的话.

提供参数类型是可选的(但建议).但是,由于默认值是PDO::PARAM_STR,您只需要在不是字符串时指定它.此外,这里PDO将照顾长度 - 没有长度参数.

$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();
...
Run Code Online (Sandbox Code Playgroud)

我通常更喜欢这种方法.我发现它是最干净,最灵活的.

使用bindParam()一个接一个

变量绑定到占位符,在执行查询时将读取,而不是在调用bindParam()时.这可能是也可能不是你想要的.当您想要使用不同的值重复执行查询时,它会派上用场.

$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();
  ...
}
Run Code Online (Sandbox Code Playgroud)

您只准备和绑定一次保护CPU周期.:)

同时使用命名占位符

你只需要插入一个数组execute().每个键都是查询中的命名占位符(请参阅Bill Karwins的回答).数组的顺序并不重要.

旁注:使用此方法,您无法为PDO提供数据类型提示(PDO :: PARAM_INT等).AFAIK,PDO试图猜测.

一次性使用匿名占位符

您还将数组放入execute(),但它是数字索引(没有字符串键).这些值将按照它们在查询/数组中出现的顺序逐个替换您的匿名占位符 - 第一个数组值替换第一个占位符,依此类推.请参阅erm410的回答.

与数组和命名占位符一样,您无法提供数据类型提示.

他们有什么共同点

  • 所有这些都要求您绑定/提供与占位符一样多的值.如果你绑定太少/很少,PDO会吃掉你的孩子.
  • 您不必关心转义,PDO处理它.准备好的PDO语句是SQL注入安全设计.但是,对于exec()query(),情况并非如此- 通常只应将这两个用于硬编码查询.

另请注意,PDO会抛出异常.这些可以向用户揭示潜在的敏感信息.您至少应该将初始PDO设置放在try/catch块中!

如果您不希望它稍后抛出异常,则可以将错误模式设置为警告.

try {
  $db = new PDO(...);
  $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING)
} catch (PDOException $e) {
  echo 'Oops, something went wrong with the database connection.';
}
Run Code Online (Sandbox Code Playgroud)