[英]Replacing mysql_* functions with PDO and prepared statements
我总是完成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*');
在使用这个时,我总是使用简单的方法在进行查询之前转义任何数据,无论是使用mysql_real_escape_string
进行INSERT
, SELECT
, UPDATE
还是DELETE
$name = $_POST['name'];
$name = mysql_real_escape_string($name);
$sql = mysql_query("SELECT * FROM `users` WHERE (`name` = '$name')") or die(mysql_error());
现在我明白这在某种程度上是安全的!
它逃脱了危险的人物; 但是,它仍然容易受到其他可能包含安全字符的攻击,但可能对显示数据或在某些情况下恶意修改或删除数据有害。
所以,我搜索了一下,发现了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';
}
现在,函数名称不同,所以我的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);
但后来我坚持了几件事情,如果变量不是一个数字并且是一串文本,你必须在PDO:PARAM_STR
之后给出一个长度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."");
现在,这一切都安全吗?
如果我是对的,插入数据会是相同的,例如:
$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();
那会有用吗,那也安全吗? 如果它是正确的,我会为?_LENGTH_?
输入什么价值?_LENGTH_?
? 我完全错了吗?
UPDATE
到目前为止我的回复非常有帮助,不能谢谢你们! 每个人都有一个+1打开我的眼睛到一些有点不同的东西。 很难选择最佳答案,但我认为Col. Shrapnel应该得到它,因为一切都被覆盖,甚至进入其他阵列,我不知道自定义库!
但多亏你们所有人:)
感谢有趣的问题。 干得好:
它摆脱了危险的角色,
你的概念是完全错误的。
事实上“危险人物”是一个神话,没有。 并且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);
但是,当然您也可以使用预准备语句使用相同的代码。
* (yes I am aware of the Schiflett's scaring tales)
我从不打扰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) );
这同样有效,也更容易编码。
您可能也对我的演示文稿SQL注入神话和谬误 ,或我的书SQL反模式:避免数据库编程的陷阱感兴趣。
是的,:某些东西是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();
...
我通常更喜欢这种方法。 我发现它是最干净,最灵活的。
使用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();
...
}
您只准备和绑定一次保护CPU周期。 :)
同时使用命名占位符
你只需要插入一个数组来execute()
。 每个键都是查询中的命名占位符(请参阅Bill Karwins的回答)。 数组的顺序并不重要。
旁注:使用此方法,您无法为PDO提供数据类型提示(PDO :: PARAM_INT等)。 AFAIK,PDO试图猜测。
一次性使用匿名占位符
您还将一个数组放入execute(),但它是用数字索引的(没有字符串键)。 这些值将按照它们在查询/数组中出现的顺序逐个替换您的匿名占位符 - 第一个数组值替换第一个占位符,依此类推。 请参阅erm410的回答。
与数组和命名占位符一样,您无法提供数据类型提示。
他们有什么共同点
另请注意, 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.';
}
要回答长度问题,指定它是可选的,除非您绑定的参数是存储过程的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()的数组一样有效(参见http://www.php.net/manual/en/pdostatement.execute.php )。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.