用PDOreplacemysql_ *函数并准备语句
我一直做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*');
在使用这个函数的时候,我总是使用简单的方法在查询之前转义任何数据,无论是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());
现在我明白这在一定程度上是安全的!
它逃脱了危险的人物; 但是,它仍然容易受到其他可能包含安全字符的攻击,但可能会对显示数据或在某些情况下恶意修改或删除数据有害。
所以,我search了一下,发现了PDO,MySQLi和准备好的语句。 是的,我可能会迟到,但是我已经阅读了许多教程(tizag,W3C,博客,Googlesearch),没有一个提到过这些。 这似乎很奇怪,为什么只是逃避用户的input实际上是不安全的,不好的做法至less可以说。 是的,我知道你可以使用正则expression式来解决这个问题,但是我相信这还不够?
根据我的理解,当用户inputvariables时,使用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
等工作。 所以我不得不阅读/记住一些新的,但这是我感到困惑。
如果我想插入注册/registry格中的数据,我将如何去做这件事,但主要是如何安全地去做这件事? 我认为这是准备好的语句进来,但通过使用它们,这消除了使用像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);
但是,如果variables不是一个数字并且是一串文本,那么我就被困在了一些事情上,如果我没有弄错,你必须在PDO:PARAM_STR
之后给出一个长度PDO:PARAM_STR
。 但是,如果您不确定用户input的数据给出的价值,那么如何给定一定的长度? 无论哪种方式,据我所知,显示您的数据,然后做
$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_?
? 我有这个完全错误吗?
UPDATE
我到目前为止的回复非常有帮助,不能感谢你们! 每个人都有一个+1来打开我的眼睛有点不同。 很难select最好的答案,但是我认为Colles Shrapnel值得拥有,因为所有的东西都被覆盖了,甚至进入其他有我不知道的自定义库的数组!
但是,多亏了你们:)
感谢有趣的问题。 干得好:
它逃脱了危险的angular色,
你的概念是完全错误的。
其实“危险人物”是一个神话,没有一个。 而mysql_real_escape_string转义,但只是一个string分隔符 。 从这个定义中可以得出结论:它只适用于string 。
但是,它仍然容易受到其他可能包含安全字符的攻击,但可能会对显示数据或在某些情况下恶意修改或删除数据有害。
你在这里混合一切。
说起数据库,
- 对于string,它不是脆弱的。 只要您的string被引用和转义, 就不能 “恶意修改或删除数据”。
*
- 对于其他数据types数据 – 是的,这是没用的 。 但不是因为它有些“不安全”,而是因为使用不当。
至于显示数据,我认为在PDO相关的问题上是不重要的,因为PDO与显示数据无关。
转义用户input
^^^另一个妄想要注意!
-
用户input与转义完全无关 。 正如你可以从前面的定义中学到的,你必须转义string,而不是“用户input”。 所以,再次:
- 无论来源如何,你都有逃脱的string
- 无论来源如何,逃避其他types的数据都是无用的。
明白了吗?
现在,我希望你了解逃跑的局限性和“危险人物”的误解。
根据我的理解,使用PDO /准备好的语句更安全
不是真的。
实际上,我们可以dynamic添加四个不同的查询部分:
- 一个string
- 一个号码
- 一个标识符
- 一个语法关键字。
所以,你可以看到逃跑只涉及一个问题。 (但是,当然,如果你把数字当作string(把它们放在引号中), 在适用时也可以使它们安全)
准备好的陈述覆盖 – 呃 – 整个2个主题! 一个大问题;-)
对于其他两个问题,看我以前的答案, 在PHP中提交string数据库时应该照顾非法字符使用htmlspecialchars()或使用正则expression式?
现在,函数名称是不同的,所以不再是我的mysql_query,mysql_fetch_array,mysql_num_rows等工作。
这是另一个PHP 用户的严重错觉,一场自然灾难,一场灾难:
即使使用旧的mysql驱动程序, 也不应该在其代码中使用纯粹的API函数 ! 一个必须把他们在一些图书馆function的日常使用! (不是一些魔法,而只是为了缩短代码,减less重复,防错,更一致和可读)。
PDO也是一样的!
现在再次提出你的问题。
但通过使用它,这消除了使用像mysql_real_escape_string的东西的需要?
是。
但是我认为这大致是从数据库中获取用户应该做什么的概念
不要获取,而是向查询添加任何数据 !
如果我没有弄错,你必须在PDO之后给出长度:PARAM_STR
你可以,但你不必。
现在,这是安全的吗?
在数据库安全方面,这个代码中没有任何弱点。 没有什么可以保护的。
为了显示安全性 – 只要search这个网站的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()或参数types或长度。
我只是传递一个参数值的数组来执行(),就像这样:
$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')
这样的硬编码string。
提供参数types是可选的(但build议)。 但是,由于默认是PDO::PARAM_STR
,所以只需要在不是string时指定它。 此外, 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()一个接一个
一个variables绑定到您的占位符,当执行查询时将被读取,而不是在调用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的答案)。 数组的顺序并不重要。
注意:使用这种方法你不能提供数据types提示(PDO :: PARAM_INT等)的PDO。 AFAIK,PDO试图猜测。
一次用匿名占位符
你也放入一个数组来执行(),但是它的数字索引(没有string键)。 这些值将按照它们出现在您的查询/数组中的顺序逐个replace您的匿名占位符 – 第一个数组值将replace第一个占位符,等等。 看到erm410的答案。
与数组和命名占位符一样,您不能提供数据types提示。
他们有什么共同点
- 所有这些要求您绑定/提供尽可能多的值,因为你有占位符。 如果你绑定太多/很less,PDO会吃你的孩子。
- 您不必关心逃跑,PDO处理。 准备好的PDO语句是SQL注入安全的devise。 但是, exec()和query()不适用 – 通常应该只使用这两个硬编码查询。
另请注意, PDO引发exception 。 这些可能会向用户透露潜在的敏感信息。 你应该至less把你的初始PDO设置在try / catch块中 !
如果您不希望稍后引发exception,则可以将错误模式设置为警告。
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参数,所以在大多数情况下,您可以放心地忽略它。
就安全性而言,当绑定参数时,在幕后进行转义。 这是可能的,因为您在创build对象时必须创build数据库连接。 您还可以防止SQL注入攻击,因为通过准备语句,您可以在用户input可以到达任何位置之前告诉数据库该语句的格式。 一个例子:
$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' ie returns empty set. */
因此,就安全性而言,上面的例子看起来很好。
最后,我同意单独的绑定参数是单调乏味的,并且与传递给PDOStatement-> execute()的数组一样有效(参见http://www.php.net/manual/en/pdostatement.execute.php )。