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)
我从不打扰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反模式:避免数据库编程的陷阱感兴趣.
是的,:某些东西是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设置放在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)